From d8fa9de93a1cac64eb30060a2c2319896341ae1a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Timm=20B=C3=A4der?= Date: Fri, 25 Dec 2020 12:05:28 +0100 Subject: [PATCH] label: Modernize source file Try to sort toplevel functions to minimize unnecessary function prototypes at the beginning of the file, get rid of all tabs and trailing whitespace. --- gtk/gtklabel.c | 6180 ++++++++++++++++++++--------------------- gtk/gtklabelprivate.h | 4 +- 2 files changed, 3019 insertions(+), 3165 deletions(-) diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c index 19b0386c05..ef655b29e1 100644 --- a/gtk/gtklabel.c +++ b/gtk/gtklabel.c @@ -407,168 +407,31 @@ static guint signals[LAST_SIGNAL] = { 0 }; static GQuark quark_mnemonics_visible_connected; -static void gtk_label_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void gtk_label_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); -static void gtk_label_finalize (GObject *object); -static void gtk_label_dispose (GObject *object); -static void gtk_label_size_allocate (GtkWidget *widget, - int width, - int height, - int baseline); -static void gtk_label_state_flags_changed (GtkWidget *widget, - GtkStateFlags prev_state); -static void gtk_label_css_changed (GtkWidget *widget, - GtkCssStyleChange *change); -static void gtk_label_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot); -static gboolean gtk_label_focus (GtkWidget *widget, - GtkDirectionType direction); - -static void gtk_label_unrealize (GtkWidget *widget); - -static void gtk_label_motion (GtkEventControllerMotion *controller, - double x, - double y, - gpointer data); -static void gtk_label_leave (GtkEventControllerMotion *controller, - gpointer data); - -static gboolean gtk_label_grab_focus (GtkWidget *widget); - -static gboolean gtk_label_query_tooltip (GtkWidget *widget, - int x, - int y, - gboolean keyboard_tip, - GtkTooltip *tooltip); - -static void gtk_label_set_text_internal (GtkLabel *self, - char *str); -static gboolean gtk_label_set_label_internal (GtkLabel *self, - const char *str); -static gboolean gtk_label_set_use_markup_internal (GtkLabel *self, - gboolean val); -static gboolean gtk_label_set_use_underline_internal (GtkLabel *self, - gboolean val); static void gtk_label_set_markup_internal (GtkLabel *self, - const char *str, - gboolean with_uline); + const char *str, + gboolean with_uline); static void gtk_label_recalculate (GtkLabel *self); -static void gtk_label_root (GtkWidget *widget); -static void gtk_label_unroot (GtkWidget *widget); -static void gtk_label_popup_menu (GtkWidget *widget, - const char *action_name, - GVariant *parameters); static void gtk_label_do_popup (GtkLabel *self, double x, double y); - static void gtk_label_ensure_select_info (GtkLabel *self); static void gtk_label_clear_select_info (GtkLabel *self); -static void gtk_label_update_cursor (GtkLabel *self); static void gtk_label_clear_layout (GtkLabel *self); static void gtk_label_ensure_layout (GtkLabel *self); static void gtk_label_select_region_index (GtkLabel *self, int anchor_index, int end_index); - static void gtk_label_update_active_link (GtkWidget *widget, double x, double y); - -static gboolean gtk_label_mnemonic_activate (GtkWidget *widget, - gboolean group_cycling); static void gtk_label_setup_mnemonic (GtkLabel *self); static void gtk_label_buildable_interface_init (GtkBuildableIface *iface); -static gboolean gtk_label_buildable_custom_tag_start (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *child, - const char *tagname, - GtkBuildableParser *parser, - gpointer *data); - -static void gtk_label_buildable_custom_finished (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *child, - const char *tagname, - gpointer user_data); - /* For selectable labels: */ static void gtk_label_move_cursor (GtkLabel *self, - GtkMovementStep step, - int count, - gboolean extend_selection); -static void gtk_label_copy_clipboard (GtkLabel *self); -static void gtk_label_select_all (GtkLabel *self); -static int gtk_label_move_forward_word (GtkLabel *self, - int start); -static int gtk_label_move_backward_word (GtkLabel *self, - int start); - -/* For links: */ -static void gtk_label_clear_links (GtkLabel *self); -static gboolean gtk_label_activate_link (GtkLabel *self, - const char *uri); -static void gtk_label_activate_current_link (GtkLabel *self); -static void emit_activate_link (GtkLabel *self, - GtkLabelLink *link); - -/* Event controller callbacks */ -static void gtk_label_click_gesture_pressed (GtkGestureClick *gesture, - int n_press, - double x, - double y, - GtkLabel *self); -static void gtk_label_click_gesture_released (GtkGestureClick *gesture, - int n_press, - double x, - double y, - GtkLabel *self); -static void gtk_label_drag_gesture_begin (GtkGestureDrag *gesture, - double start_x, - double start_y, - GtkLabel *self); -static void gtk_label_drag_gesture_update (GtkGestureDrag *gesture, - double offset_x, - double offset_y, - GtkLabel *self); - -/* Actions */ - -static void gtk_label_activate_clipboard_copy (GtkWidget *self, - const char *name, - GVariant *parameter); -static void gtk_label_activate_selection_select_all (GtkWidget *self, - const char *name, - GVariant *parameter); -static void gtk_label_activate_link_open (GtkWidget *self, - const char *name, - GVariant *parameter); -static void gtk_label_activate_link_copy (GtkWidget *self, - const char *name, - GVariant *parameter); -static void gtk_label_nop (GtkWidget *self, - const char *name, - GVariant *parameter); - -static void gtk_label_update_actions (GtkLabel *self); - -static GtkSizeRequestMode gtk_label_get_request_mode (GtkWidget *widget); -static void gtk_label_measure (GtkWidget *widget, - GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline); - - + GtkMovementStep step, + int count, + gboolean extend_selection); static GtkBuildableIface *buildable_parent_iface = NULL; @@ -578,2987 +441,3168 @@ G_DEFINE_TYPE_WITH_CODE (GtkLabel, gtk_label, GTK_TYPE_WIDGET, static void add_move_binding (GtkWidgetClass *widget_class, - guint keyval, - guint modmask, - GtkMovementStep step, - int count) + guint keyval, + guint modmask, + GtkMovementStep step, + int count) { g_return_if_fail ((modmask & GDK_SHIFT_MASK) == 0); - + gtk_widget_class_add_binding_signal (widget_class, keyval, modmask, - "move-cursor", + "move-cursor", "(iib)", step, count, FALSE); /* Selection-extending version */ gtk_widget_class_add_binding_signal (widget_class, keyval, modmask | GDK_SHIFT_MASK, - "move-cursor", + "move-cursor", "(iib)", step, count, TRUE); } static void -gtk_label_class_init (GtkLabelClass *class) +gtk_label_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) { - GObjectClass *gobject_class = G_OBJECT_CLASS (class); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + GtkLabel *self = GTK_LABEL (object); - gobject_class->set_property = gtk_label_set_property; - gobject_class->get_property = gtk_label_get_property; - gobject_class->finalize = gtk_label_finalize; - gobject_class->dispose = gtk_label_dispose; + switch (prop_id) + { + case PROP_LABEL: + gtk_label_set_label (self, g_value_get_string (value)); + break; + case PROP_ATTRIBUTES: + gtk_label_set_attributes (self, g_value_get_boxed (value)); + break; + case PROP_USE_MARKUP: + gtk_label_set_use_markup (self, g_value_get_boolean (value)); + break; + case PROP_USE_UNDERLINE: + gtk_label_set_use_underline (self, g_value_get_boolean (value)); + break; + case PROP_JUSTIFY: + gtk_label_set_justify (self, g_value_get_enum (value)); + break; + case PROP_WRAP: + gtk_label_set_wrap (self, g_value_get_boolean (value)); + break; + case PROP_WRAP_MODE: + gtk_label_set_wrap_mode (self, g_value_get_enum (value)); + break; + case PROP_SELECTABLE: + gtk_label_set_selectable (self, g_value_get_boolean (value)); + break; + case PROP_MNEMONIC_WIDGET: + gtk_label_set_mnemonic_widget (self, (GtkWidget*) g_value_get_object (value)); + break; + case PROP_ELLIPSIZE: + gtk_label_set_ellipsize (self, g_value_get_enum (value)); + break; + case PROP_WIDTH_CHARS: + gtk_label_set_width_chars (self, g_value_get_int (value)); + break; + case PROP_SINGLE_LINE_MODE: + gtk_label_set_single_line_mode (self, g_value_get_boolean (value)); + break; + case PROP_MAX_WIDTH_CHARS: + gtk_label_set_max_width_chars (self, g_value_get_int (value)); + break; + case PROP_LINES: + gtk_label_set_lines (self, g_value_get_int (value)); + break; + case PROP_XALIGN: + gtk_label_set_xalign (self, g_value_get_float (value)); + break; + case PROP_YALIGN: + gtk_label_set_yalign (self, g_value_get_float (value)); + break; + case PROP_EXTRA_MENU: + gtk_label_set_extra_menu (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} - widget_class->size_allocate = gtk_label_size_allocate; - widget_class->state_flags_changed = gtk_label_state_flags_changed; - widget_class->css_changed = gtk_label_css_changed; - widget_class->query_tooltip = gtk_label_query_tooltip; - widget_class->snapshot = gtk_label_snapshot; - widget_class->unrealize = gtk_label_unrealize; - widget_class->root = gtk_label_root; - widget_class->unroot = gtk_label_unroot; - widget_class->mnemonic_activate = gtk_label_mnemonic_activate; - widget_class->grab_focus = gtk_label_grab_focus; - widget_class->focus = gtk_label_focus; - widget_class->get_request_mode = gtk_label_get_request_mode; - widget_class->measure = gtk_label_measure; +static void +gtk_label_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkLabel *self = GTK_LABEL (object); - class->move_cursor = gtk_label_move_cursor; - class->copy_clipboard = gtk_label_copy_clipboard; - class->activate_link = gtk_label_activate_link; + switch (prop_id) + { + case PROP_LABEL: + g_value_set_string (value, self->label); + break; + case PROP_ATTRIBUTES: + g_value_set_boxed (value, self->attrs); + break; + case PROP_USE_MARKUP: + g_value_set_boolean (value, self->use_markup); + break; + case PROP_USE_UNDERLINE: + g_value_set_boolean (value, self->use_underline); + break; + case PROP_JUSTIFY: + g_value_set_enum (value, self->jtype); + break; + case PROP_WRAP: + g_value_set_boolean (value, self->wrap); + break; + case PROP_WRAP_MODE: + g_value_set_enum (value, self->wrap_mode); + break; + case PROP_SELECTABLE: + g_value_set_boolean (value, gtk_label_get_selectable (self)); + break; + case PROP_MNEMONIC_KEYVAL: + g_value_set_uint (value, self->mnemonic_keyval); + break; + case PROP_MNEMONIC_WIDGET: + g_value_set_object (value, (GObject*) self->mnemonic_widget); + break; + case PROP_ELLIPSIZE: + g_value_set_enum (value, self->ellipsize); + break; + case PROP_WIDTH_CHARS: + g_value_set_int (value, gtk_label_get_width_chars (self)); + break; + case PROP_SINGLE_LINE_MODE: + g_value_set_boolean (value, gtk_label_get_single_line_mode (self)); + break; + case PROP_MAX_WIDTH_CHARS: + g_value_set_int (value, gtk_label_get_max_width_chars (self)); + break; + case PROP_LINES: + g_value_set_int (value, gtk_label_get_lines (self)); + break; + case PROP_XALIGN: + g_value_set_float (value, gtk_label_get_xalign (self)); + break; + case PROP_YALIGN: + g_value_set_float (value, gtk_label_get_yalign (self)); + break; + case PROP_EXTRA_MENU: + g_value_set_object (value, gtk_label_get_extra_menu (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} - /** - * GtkLabel::move-cursor: - * @entry: the object which received the signal - * @step: the granularity of the move, as a #GtkMovementStep - * @count: the number of @step units to move - * @extend_selection: %TRUE if the move should extend the selection - * - * The ::move-cursor signal is a - * [keybinding signal][GtkSignalAction] - * which gets emitted when the user initiates a cursor movement. - * If the cursor is not visible in @entry, this signal causes - * the viewport to be moved instead. - * - * Applications should not connect to it, but may emit it with - * g_signal_emit_by_name() if they need to control the cursor - * programmatically. - * - * The default bindings for this signal come in two variants, - * the variant with the Shift modifier extends the selection, - * the variant without the Shift modifier does not. - * There are too many key combinations to list them all here. - * - Arrow keys move by individual characters/lines - * - Ctrl-arrow key combinations move by words/paragraphs - * - Home/End keys move to the ends of the buffer - */ - signals[MOVE_CURSOR] = - g_signal_new (I_("move-cursor"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkLabelClass, move_cursor), - NULL, NULL, - _gtk_marshal_VOID__ENUM_INT_BOOLEAN, - G_TYPE_NONE, 3, - GTK_TYPE_MOVEMENT_STEP, - G_TYPE_INT, - G_TYPE_BOOLEAN); +static void +gtk_label_init (GtkLabel *self) +{ + self->width_chars = -1; + self->max_width_chars = -1; + self->label = g_strdup (""); + self->lines = -1; - /** - * GtkLabel::copy-clipboard: - * @self: the object which received the signal - * - * The ::copy-clipboard signal is a - * [keybinding signal][GtkSignalAction] - * which gets emitted to copy the selection to the clipboard. - * - * The default binding for this signal is Ctrl-c. - */ - signals[COPY_CLIPBOARD] = - g_signal_new (I_("copy-clipboard"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkLabelClass, copy_clipboard), - NULL, NULL, - NULL, - G_TYPE_NONE, 0); - - /** - * GtkLabel::activate-current-link: - * @self: The label on which the signal was emitted - * - * A [keybinding signal][GtkSignalAction] - * which gets emitted when the user activates a link in the label. - * - * Applications may also emit the signal with g_signal_emit_by_name() - * if they need to control activation of URIs programmatically. - * - * The default bindings for this signal are all forms of the Enter key. - */ - signals[ACTIVATE_CURRENT_LINK] = - g_signal_new_class_handler (I_("activate-current-link"), - G_TYPE_FROM_CLASS (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_CALLBACK (gtk_label_activate_current_link), - NULL, NULL, - NULL, - G_TYPE_NONE, 0); + self->xalign = 0.5; + self->yalign = 0.5; - /** - * GtkLabel::activate-link: - * @self: The label on which the signal was emitted - * @uri: the URI that is activated - * - * The signal which gets emitted to activate a URI. - * Applications may connect to it to override the default behaviour, - * which is to call gtk_show_uri(). - * - * Returns: %TRUE if the link has been activated - */ - signals[ACTIVATE_LINK] = - g_signal_new (I_("activate-link"), - G_TYPE_FROM_CLASS (gobject_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GtkLabelClass, activate_link), - _gtk_boolean_handled_accumulator, NULL, - _gtk_marshal_BOOLEAN__STRING, - G_TYPE_BOOLEAN, 1, G_TYPE_STRING); + self->jtype = GTK_JUSTIFY_LEFT; + self->wrap = FALSE; + self->wrap_mode = PANGO_WRAP_WORD; + self->ellipsize = PANGO_ELLIPSIZE_NONE; - /** - * GtkLabel:label: - * - * The contents of the label. - * - * If the string contains [Pango XML markup][PangoMarkupFormat], you will - * have to set the #GtkLabel:use-markup property to %TRUE in order for the - * label to display the markup attributes. See also gtk_label_set_markup() - * for a convenience function that sets both this property and the - * #GtkLabel:use-markup property at the same time. - * - * If the string contains underlines acting as mnemonics, you will have to - * set the #GtkLabel:use-underline property to %TRUE in order for the label - * to display them. - */ - label_props[PROP_LABEL] = - g_param_spec_string ("label", - P_("Label"), - P_("The text of the label"), - "", - GTK_PARAM_READWRITE); + self->use_underline = FALSE; + self->use_markup = FALSE; - label_props[PROP_ATTRIBUTES] = - g_param_spec_boxed ("attributes", - P_("Attributes"), - P_("A list of style attributes to apply to the text of the label"), - PANGO_TYPE_ATTR_LIST, - GTK_PARAM_READWRITE); + self->mnemonic_keyval = GDK_KEY_VoidSymbol; + self->layout = NULL; + self->text = g_strdup (""); + self->attrs = NULL; - label_props[PROP_USE_MARKUP] = - g_param_spec_boolean ("use-markup", - P_("Use markup"), - P_("The text of the label includes XML markup. See pango_parse_markup()"), - FALSE, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + self->mnemonic_widget = NULL; - label_props[PROP_USE_UNDERLINE] = - g_param_spec_boolean ("use-underline", - P_("Use underline"), - P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"), - FALSE, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + self->mnemonics_visible = FALSE; +} - label_props[PROP_JUSTIFY] = - g_param_spec_enum ("justify", - P_("Justification"), - P_("The alignment of the lines in the text of the label relative to each other. This does NOT affect the alignment of the label within its allocation. See GtkLabel:xalign for that"), - GTK_TYPE_JUSTIFICATION, - GTK_JUSTIFY_LEFT, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); +static const GtkBuildableParser pango_parser = +{ + gtk_pango_attribute_start_element, +}; - /** - * GtkLabel:xalign: - * - * The xalign property determines the horizontal alignment of the label text - * inside the labels size allocation. Compare this to #GtkWidget:halign, - * which determines how the labels size allocation is positioned in the - * space available for the label. - */ - label_props[PROP_XALIGN] = - g_param_spec_float ("xalign", - P_("X align"), - P_("The horizontal alignment, from 0 (left) to 1 (right). Reversed for RTL layouts."), - 0.0, 1.0, - 0.5, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); +static gboolean +gtk_label_buildable_custom_tag_start (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const char *tagname, + GtkBuildableParser *parser, + gpointer *data) +{ + if (buildable_parent_iface->custom_tag_start (buildable, builder, child, + tagname, parser, data)) + return TRUE; - /** - * GtkLabel:yalign: - * - * The yalign property determines the vertical alignment of the label text - * inside the labels size allocation. Compare this to #GtkWidget:valign, - * which determines how the labels size allocation is positioned in the - * space available for the label. - */ - label_props[PROP_YALIGN] = - g_param_spec_float ("yalign", - P_("Y align"), - P_("The vertical alignment, from 0 (top) to 1 (bottom)"), - 0.0, 1.0, - 0.5, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + if (strcmp (tagname, "attributes") == 0) + { + GtkPangoAttributeParserData *parser_data; - label_props[PROP_WRAP] = - g_param_spec_boolean ("wrap", - P_("Line wrap"), - P_("If set, wrap lines if the text becomes too wide"), - FALSE, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + parser_data = g_slice_new0 (GtkPangoAttributeParserData); + parser_data->builder = g_object_ref (builder); + parser_data->object = (GObject *) g_object_ref (buildable); + *parser = pango_parser; + *data = parser_data; + return TRUE; + } + return FALSE; +} - /** - * GtkLabel:wrap-mode: - * - * If line wrapping is on (see the #GtkLabel:wrap property) this controls - * how the line wrapping is done. The default is %PANGO_WRAP_WORD, which - * means wrap on word boundaries. - */ - label_props[PROP_WRAP_MODE] = - g_param_spec_enum ("wrap-mode", - P_("Line wrap mode"), - P_("If wrap is set, controls how linewrapping is done"), - PANGO_TYPE_WRAP_MODE, - PANGO_WRAP_WORD, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); +static void +gtk_label_buildable_custom_finished (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const char *tagname, + gpointer user_data) +{ + GtkPangoAttributeParserData *data = user_data; - label_props[PROP_SELECTABLE] = - g_param_spec_boolean ("selectable", - P_("Selectable"), - P_("Whether the label text can be selected with the mouse"), - FALSE, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + buildable_parent_iface->custom_finished (buildable, builder, child, + tagname, user_data); - label_props[PROP_MNEMONIC_KEYVAL] = - g_param_spec_uint ("mnemonic-keyval", - P_("Mnemonic key"), - P_("The mnemonic accelerator key for this label"), - 0, G_MAXUINT, - GDK_KEY_VoidSymbol, - GTK_PARAM_READABLE); + if (strcmp (tagname, "attributes") == 0) + { + if (data->attrs) + { + gtk_label_set_attributes (GTK_LABEL (buildable), data->attrs); + pango_attr_list_unref (data->attrs); + } - label_props[PROP_MNEMONIC_WIDGET] = - g_param_spec_object ("mnemonic-widget", - P_("Mnemonic widget"), - P_("The widget to be activated when the label’s mnemonic key is pressed"), - GTK_TYPE_WIDGET, - GTK_PARAM_READWRITE); + g_object_unref (data->object); + g_object_unref (data->builder); + g_slice_free (GtkPangoAttributeParserData, data); + } +} - /** - * GtkLabel:ellipsize: - * - * The preferred place to ellipsize the string, if the label does - * not have enough room to display the entire string, specified as a - * #PangoEllipsizeMode. - * - * Note that setting this property to a value other than - * %PANGO_ELLIPSIZE_NONE has the side-effect that the label requests - * only enough space to display the ellipsis "...". In particular, this - * means that ellipsizing labels do not work well in notebook tabs, unless - * the #GtkNotebook tab-expand child property is set to %TRUE. Other ways - * to set a label's width are gtk_widget_set_size_request() and - * gtk_label_set_width_chars(). - */ - label_props[PROP_ELLIPSIZE] = - g_param_spec_enum ("ellipsize", - P_("Ellipsize"), - P_("The preferred place to ellipsize the string, if the label does not have enough room to display the entire string"), - PANGO_TYPE_ELLIPSIZE_MODE, - PANGO_ELLIPSIZE_NONE, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); +static void +gtk_label_buildable_interface_init (GtkBuildableIface *iface) +{ + buildable_parent_iface = g_type_interface_peek_parent (iface); - /** - * GtkLabel:width-chars: - * - * The desired width of the label, in characters. If this property is set to - * -1, the width will be calculated automatically. - * - * See the section on [text layout][label-text-layout] - * for details of how #GtkLabel:width-chars and #GtkLabel:max-width-chars - * determine the width of ellipsized and wrapped labels. - **/ - label_props[PROP_WIDTH_CHARS] = - g_param_spec_int ("width-chars", - P_("Width In Characters"), - P_("The desired width of the label, in characters"), - -1, G_MAXINT, - -1, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + iface->custom_tag_start = gtk_label_buildable_custom_tag_start; + iface->custom_finished = gtk_label_buildable_custom_finished; +} - /** - * GtkLabel:single-line-mode: - * - * Whether the label is in single line mode. In single line mode, - * the height of the label does not depend on the actual text, it - * is always set to ascent + descent of the font. This can be an - * advantage in situations where resizing the label because of text - * changes would be distracting, e.g. in a statusbar. - **/ - label_props[PROP_SINGLE_LINE_MODE] = - g_param_spec_boolean ("single-line-mode", - P_("Single Line Mode"), - P_("Whether the label is in single line mode"), - FALSE, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); +static void +update_link_state (GtkLabel *self) +{ + GtkStateFlags state; + guint i; - /** - * GtkLabel:max-width-chars: - * - * The desired maximum width of the label, in characters. If this property - * is set to -1, the width will be calculated automatically. - * - * See the section on [text layout][label-text-layout] - * for details of how #GtkLabel:width-chars and #GtkLabel:max-width-chars - * determine the width of ellipsized and wrapped labels. - **/ - label_props[PROP_MAX_WIDTH_CHARS] = - g_param_spec_int ("max-width-chars", - P_("Maximum Width In Characters"), - P_("The desired maximum width of the label, in characters"), - -1, G_MAXINT, - -1, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + if (!self->select_info) + return; - /** - * GtkLabel:lines: - * - * The number of lines to which an ellipsized, wrapping label - * should be limited. This property has no effect if the - * label is not wrapping or ellipsized. Set this property to - * -1 if you don't want to limit the number of lines. - */ - label_props[PROP_LINES] = - g_param_spec_int ("lines", - P_("Number of lines"), - P_("The desired number of lines, when ellipsizing a wrapping label"), - -1, G_MAXINT, - -1, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + for (i = 0; i < self->select_info->n_links; i++) + { + const GtkLabelLink *link = &self->select_info->links[i]; - /** - * GtkLabel:extra-menu: - * - * A menu model whose contents will be appended to - * the context menu. - */ - label_props[PROP_EXTRA_MENU] = - g_param_spec_object ("extra-menu", - P_("Extra menu"), - P_("Menu model to append to the context menu"), - G_TYPE_MENU_MODEL, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + state = gtk_widget_get_state_flags (GTK_WIDGET (self)); + if (link->visited) + state |= GTK_STATE_FLAG_VISITED; + else + state |= GTK_STATE_FLAG_LINK; + if (link == self->select_info->active_link) + { + if (self->select_info->link_clicked) + state |= GTK_STATE_FLAG_ACTIVE; + else + state |= GTK_STATE_FLAG_PRELIGHT; + } + gtk_css_node_set_state (link->cssnode, state); + } +} - g_object_class_install_properties (gobject_class, NUM_PROPERTIES, label_props); +static void +gtk_label_update_cursor (GtkLabel *self) +{ + GtkWidget *widget = GTK_WIDGET (self); - /** - * GtkLabel|menu.popup: - * - * Opens the context menu. - */ - gtk_widget_class_install_action (widget_class, "menu.popup", NULL, gtk_label_popup_menu); + if (!self->select_info) + return; - /* - * Key bindings - */ + if (gtk_widget_is_sensitive (widget)) + { + if (self->select_info->active_link) + gtk_widget_set_cursor_from_name (widget, "pointer"); + else if (self->select_info->selectable) + gtk_widget_set_cursor_from_name (widget, "text"); + else + gtk_widget_set_cursor (widget, NULL); + } + else + gtk_widget_set_cursor (widget, NULL); +} - gtk_widget_class_add_binding_action (widget_class, - GDK_KEY_F10, GDK_SHIFT_MASK, - "menu.popup", - NULL); - gtk_widget_class_add_binding_action (widget_class, - GDK_KEY_Menu, 0, - "menu.popup", - NULL); +static void +gtk_label_state_flags_changed (GtkWidget *widget, + GtkStateFlags prev_state) +{ + GtkLabel *self = GTK_LABEL (widget); - /* Moving the insertion point */ - add_move_binding (widget_class, GDK_KEY_Right, 0, - GTK_MOVEMENT_VISUAL_POSITIONS, 1); + if (self->select_info) + { + if (!gtk_widget_is_sensitive (widget)) + gtk_label_select_region (self, 0, 0); - add_move_binding (widget_class, GDK_KEY_Left, 0, - GTK_MOVEMENT_VISUAL_POSITIONS, -1); + gtk_label_update_cursor (self); + update_link_state (self); + } - add_move_binding (widget_class, GDK_KEY_KP_Right, 0, - GTK_MOVEMENT_VISUAL_POSITIONS, 1); - - add_move_binding (widget_class, GDK_KEY_KP_Left, 0, - GTK_MOVEMENT_VISUAL_POSITIONS, -1); - - add_move_binding (widget_class, GDK_KEY_f, GDK_CONTROL_MASK, - GTK_MOVEMENT_LOGICAL_POSITIONS, 1); - - add_move_binding (widget_class, GDK_KEY_b, GDK_CONTROL_MASK, - GTK_MOVEMENT_LOGICAL_POSITIONS, -1); - - add_move_binding (widget_class, GDK_KEY_Right, GDK_CONTROL_MASK, - GTK_MOVEMENT_WORDS, 1); + if (GTK_WIDGET_CLASS (gtk_label_parent_class)->state_flags_changed) + GTK_WIDGET_CLASS (gtk_label_parent_class)->state_flags_changed (widget, prev_state); +} - add_move_binding (widget_class, GDK_KEY_Left, GDK_CONTROL_MASK, - GTK_MOVEMENT_WORDS, -1); +static void +gtk_label_update_layout_attributes (GtkLabel *self, + PangoAttrList *style_attrs) +{ + GtkWidget *widget = GTK_WIDGET (self); + GtkCssStyle *style; + PangoAttrList *attrs; - add_move_binding (widget_class, GDK_KEY_KP_Right, GDK_CONTROL_MASK, - GTK_MOVEMENT_WORDS, 1); + if (self->layout == NULL) + { + pango_attr_list_unref (style_attrs); + return; + } - add_move_binding (widget_class, GDK_KEY_KP_Left, GDK_CONTROL_MASK, - GTK_MOVEMENT_WORDS, -1); + if (self->select_info && self->select_info->links) + { + guint i; - /* select all */ - gtk_widget_class_add_binding (widget_class, - GDK_KEY_a, GDK_CONTROL_MASK, - (GtkShortcutFunc) gtk_label_select_all, - NULL); - gtk_widget_class_add_binding (widget_class, - GDK_KEY_slash, GDK_CONTROL_MASK, - (GtkShortcutFunc) gtk_label_select_all, - NULL); + attrs = pango_attr_list_new (); - /* unselect all */ - gtk_widget_class_add_binding_signal (widget_class, - GDK_KEY_a, GDK_SHIFT_MASK | GDK_CONTROL_MASK, - "move-cursor", - "(iib)", GTK_MOVEMENT_PARAGRAPH_ENDS, 0, FALSE); + for (i = 0; i < self->select_info->n_links; i++) + { + const GtkLabelLink *link = &self->select_info->links[i]; + const GdkRGBA *link_color; + PangoAttrList *link_attrs; + PangoAttribute *attr; - gtk_widget_class_add_binding_signal (widget_class, - GDK_KEY_backslash, GDK_CONTROL_MASK, - "move-cursor", - "(iib)", GTK_MOVEMENT_PARAGRAPH_ENDS, 0, FALSE); + style = gtk_css_node_get_style (link->cssnode); + link_attrs = gtk_css_style_get_pango_attributes (style); + if (link_attrs) + { + GSList *attributes = pango_attr_list_get_attributes (link_attrs); + GSList *l; + for (l = attributes; l; l = l->next) + { + attr = l->data; - add_move_binding (widget_class, GDK_KEY_f, GDK_ALT_MASK, - GTK_MOVEMENT_WORDS, 1); + attr->start_index = link->start; + attr->end_index = link->end; + pango_attr_list_insert (attrs, attr); + } + g_slist_free (attributes); + } - add_move_binding (widget_class, GDK_KEY_b, GDK_ALT_MASK, - GTK_MOVEMENT_WORDS, -1); + link_color = gtk_css_color_value_get_rgba (style->core->color); + attr = pango_attr_foreground_new (link_color->red * 65535, + link_color->green * 65535, + link_color->blue * 65535); + attr->start_index = link->start; + attr->end_index = link->end; + pango_attr_list_insert (attrs, attr); - add_move_binding (widget_class, GDK_KEY_Home, 0, - GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); + pango_attr_list_unref (link_attrs); + } + } + else + attrs = NULL; - add_move_binding (widget_class, GDK_KEY_End, 0, - GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); + style = gtk_css_node_get_style (gtk_widget_get_css_node (widget)); + if (!style_attrs) + style_attrs = gtk_css_style_get_pango_attributes (style); - add_move_binding (widget_class, GDK_KEY_KP_Home, 0, - GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); + if (style_attrs) + { + attrs = _gtk_pango_attr_list_merge (attrs, style_attrs); + pango_attr_list_unref (style_attrs); + } - add_move_binding (widget_class, GDK_KEY_KP_End, 0, - GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); - - add_move_binding (widget_class, GDK_KEY_Home, GDK_CONTROL_MASK, - GTK_MOVEMENT_BUFFER_ENDS, -1); + attrs = _gtk_pango_attr_list_merge (attrs, self->markup_attrs); + attrs = _gtk_pango_attr_list_merge (attrs, self->attrs); - add_move_binding (widget_class, GDK_KEY_End, GDK_CONTROL_MASK, - GTK_MOVEMENT_BUFFER_ENDS, 1); + pango_layout_set_attributes (self->layout, attrs); - add_move_binding (widget_class, GDK_KEY_KP_Home, GDK_CONTROL_MASK, - GTK_MOVEMENT_BUFFER_ENDS, -1); + pango_attr_list_unref (attrs); +} - add_move_binding (widget_class, GDK_KEY_KP_End, GDK_CONTROL_MASK, - GTK_MOVEMENT_BUFFER_ENDS, 1); +static void +gtk_label_css_changed (GtkWidget *widget, + GtkCssStyleChange *change) +{ + GtkLabel *self = GTK_LABEL (widget); + gboolean attrs_affected; + PangoAttrList *new_attrs = NULL; - /* copy */ - gtk_widget_class_add_binding_signal (widget_class, - GDK_KEY_c, GDK_CONTROL_MASK, - "copy-clipboard", - NULL); + GTK_WIDGET_CLASS (gtk_label_parent_class)->css_changed (widget, change); - gtk_widget_class_add_binding_signal (widget_class, - GDK_KEY_Return, 0, - "activate-current-link", - NULL); - gtk_widget_class_add_binding_signal (widget_class, - GDK_KEY_ISO_Enter, 0, - "activate-current-link", - NULL); - gtk_widget_class_add_binding_signal (widget_class, - GDK_KEY_KP_Enter, 0, - "activate-current-link", - NULL); + if (gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_TEXT_ATTRS)) + { + new_attrs = gtk_css_style_get_pango_attributes (gtk_css_style_change_get_new_style (change)); + attrs_affected = (self->layout && pango_layout_get_attributes (self->layout)) || + new_attrs; + } + else + attrs_affected = FALSE; - gtk_widget_class_set_css_name (widget_class, I_("label")); - gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LABEL); + if (change == NULL || attrs_affected || (self->select_info && self->select_info->links)) + { + gtk_label_update_layout_attributes (self, new_attrs); - quark_mnemonics_visible_connected = g_quark_from_static_string ("gtk-label-mnemonics-visible-connected"); + if (attrs_affected) + gtk_widget_queue_draw (widget); + } +} - /** - * GtkLabel|clipboard.cut: - * - * Doesn't do anything, since text in labels can't be deleted. - */ - gtk_widget_class_install_action (widget_class, "clipboard.cut", NULL, - gtk_label_nop); +static PangoDirection +get_cursor_direction (GtkLabel *self) +{ + GSList *l; - /** - * GtkLabel|clipboard.copy: - * - * Copies the text to the clipboard. - */ - gtk_widget_class_install_action (widget_class, "clipboard.copy", NULL, - gtk_label_activate_clipboard_copy); + g_assert (self->select_info); - /** - * GtkLabel|clipboard.paste: - * - * Doesn't do anything, since text in labels can't be edited. - */ - gtk_widget_class_install_action (widget_class, "clipboard.paste", NULL, - gtk_label_nop); + gtk_label_ensure_layout (self); - /** - * GtkLabel|selection.delete: - * - * Doesn't do anything, since text in labels can't be deleted. - */ - gtk_widget_class_install_action (widget_class, "selection.delete", NULL, - gtk_label_nop); + for (l = pango_layout_get_lines_readonly (self->layout); l; l = l->next) + { + PangoLayoutLine *line = l->data; - /** - * GtkLabel|selection.select-all: - * - * Selects all of the text, if the label allows selection. - */ - gtk_widget_class_install_action (widget_class, "selection.select-all", NULL, - gtk_label_activate_selection_select_all); - - /** - * GtkLabel|link.open: - * - * Opens the link, when activated on a link inside the label. - */ - gtk_widget_class_install_action (widget_class, "link.open", NULL, - gtk_label_activate_link_open); + /* If self->select_info->selection_end is at the very end of + * the line, we don't know if the cursor is on this line or + * the next without looking ahead at the next line. (End + * of paragraph is different from line break.) But it's + * definitely in this paragraph, which is good enough + * to figure out the resolved direction. + */ + if (line->start_index + line->length >= self->select_info->selection_end) + return line->resolved_dir; + } - /** - * GtkLabel|link.copy: - * - * Copies the link to the clipboard, when activated on a link - * inside the label. - */ - gtk_widget_class_install_action (widget_class, "link.copy", NULL, - gtk_label_activate_link_copy); + return PANGO_DIRECTION_LTR; } -static void -gtk_label_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) +static GtkLabelLink * +gtk_label_get_focus_link (GtkLabel *self, + int *out_index) { - GtkLabel *self = GTK_LABEL (object); + GtkLabelSelectionInfo *info = self->select_info; + int link_index; - switch (prop_id) + if (!info || + info->selection_anchor != info->selection_end) + goto nope; + + link_index = _gtk_label_get_link_at (self, info->selection_anchor); + + if (link_index != -1) { - case PROP_LABEL: - gtk_label_set_label (self, g_value_get_string (value)); - break; - case PROP_ATTRIBUTES: - gtk_label_set_attributes (self, g_value_get_boxed (value)); - break; - case PROP_USE_MARKUP: - gtk_label_set_use_markup (self, g_value_get_boolean (value)); - break; - case PROP_USE_UNDERLINE: - gtk_label_set_use_underline (self, g_value_get_boolean (value)); - break; - case PROP_JUSTIFY: - gtk_label_set_justify (self, g_value_get_enum (value)); - break; - case PROP_WRAP: - gtk_label_set_wrap (self, g_value_get_boolean (value)); - break; - case PROP_WRAP_MODE: - gtk_label_set_wrap_mode (self, g_value_get_enum (value)); - break; - case PROP_SELECTABLE: - gtk_label_set_selectable (self, g_value_get_boolean (value)); - break; - case PROP_MNEMONIC_WIDGET: - gtk_label_set_mnemonic_widget (self, (GtkWidget*) g_value_get_object (value)); - break; - case PROP_ELLIPSIZE: - gtk_label_set_ellipsize (self, g_value_get_enum (value)); - break; - case PROP_WIDTH_CHARS: - gtk_label_set_width_chars (self, g_value_get_int (value)); - break; - case PROP_SINGLE_LINE_MODE: - gtk_label_set_single_line_mode (self, g_value_get_boolean (value)); - break; - case PROP_MAX_WIDTH_CHARS: - gtk_label_set_max_width_chars (self, g_value_get_int (value)); - break; - case PROP_LINES: - gtk_label_set_lines (self, g_value_get_int (value)); - break; - case PROP_XALIGN: - gtk_label_set_xalign (self, g_value_get_float (value)); - break; - case PROP_YALIGN: - gtk_label_set_yalign (self, g_value_get_float (value)); - break; - case PROP_EXTRA_MENU: - gtk_label_set_extra_menu (self, g_value_get_object (value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; + if (out_index) + *out_index = link_index; + + return &info->links[link_index]; } + +nope: + if (out_index) + *out_index = -1; + return NULL; } -static void -gtk_label_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) +/** + * gtk_label_get_measuring_layout: + * @self: the label + * @existing_layout: %NULL or an existing layout already in use. + * @width: the width to measure with in pango units, or -1 for infinite + * + * Gets a layout that can be used for measuring sizes. The returned + * layout will be identical to the label’s layout except for the + * layout’s width, which will be set to @width. Do not modify the returned + * layout. + * + * Returns: a new reference to a pango layout + **/ +static PangoLayout * +gtk_label_get_measuring_layout (GtkLabel *self, + PangoLayout *existing_layout, + int width) { - GtkLabel *self = GTK_LABEL (object); + PangoLayout *copy; - switch (prop_id) + if (existing_layout != NULL) { - case PROP_LABEL: - g_value_set_string (value, self->label); - break; - case PROP_ATTRIBUTES: - g_value_set_boxed (value, self->attrs); - break; - case PROP_USE_MARKUP: - g_value_set_boolean (value, self->use_markup); - break; - case PROP_USE_UNDERLINE: - g_value_set_boolean (value, self->use_underline); - break; - case PROP_JUSTIFY: - g_value_set_enum (value, self->jtype); - break; - case PROP_WRAP: - g_value_set_boolean (value, self->wrap); - break; - case PROP_WRAP_MODE: - g_value_set_enum (value, self->wrap_mode); - break; - case PROP_SELECTABLE: - g_value_set_boolean (value, gtk_label_get_selectable (self)); - break; - case PROP_MNEMONIC_KEYVAL: - g_value_set_uint (value, self->mnemonic_keyval); - break; - case PROP_MNEMONIC_WIDGET: - g_value_set_object (value, (GObject*) self->mnemonic_widget); - break; - case PROP_ELLIPSIZE: - g_value_set_enum (value, self->ellipsize); - break; - case PROP_WIDTH_CHARS: - g_value_set_int (value, gtk_label_get_width_chars (self)); - break; - case PROP_SINGLE_LINE_MODE: - g_value_set_boolean (value, gtk_label_get_single_line_mode (self)); - break; - case PROP_MAX_WIDTH_CHARS: - g_value_set_int (value, gtk_label_get_max_width_chars (self)); - break; - case PROP_LINES: - g_value_set_int (value, gtk_label_get_lines (self)); - break; - case PROP_XALIGN: - g_value_set_float (value, gtk_label_get_xalign (self)); - break; - case PROP_YALIGN: - g_value_set_float (value, gtk_label_get_yalign (self)); - break; - case PROP_EXTRA_MENU: - g_value_set_object (value, gtk_label_get_extra_menu (self)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; + if (existing_layout != self->layout) + { + pango_layout_set_width (existing_layout, width); + return existing_layout; + } + + g_object_unref (existing_layout); } -} -static void -gtk_label_init (GtkLabel *self) -{ - self->width_chars = -1; - self->max_width_chars = -1; - self->label = g_strdup (""); - self->lines = -1; + gtk_label_ensure_layout (self); - self->xalign = 0.5; - self->yalign = 0.5; + if (pango_layout_get_width (self->layout) == width) + { + g_object_ref (self->layout); + return self->layout; + } - self->jtype = GTK_JUSTIFY_LEFT; - self->wrap = FALSE; - self->wrap_mode = PANGO_WRAP_WORD; - self->ellipsize = PANGO_ELLIPSIZE_NONE; + /* We can use the label's own layout if we're not allocated a size yet, + * because we don't need it to be properly setup at that point. + * This way we can make use of caching upon the label's creation. + */ + if (gtk_widget_get_width (GTK_WIDGET (self)) <= 1) + { + g_object_ref (self->layout); + pango_layout_set_width (self->layout, width); + return self->layout; + } - self->use_underline = FALSE; - self->use_markup = FALSE; + /* oftentimes we want to measure a width that is far wider than the current width, + * even though the layout would not change if we made it wider. In that case, we + * can just return the current layout, because for measuring purposes, it will be + * identical. + */ + if (!pango_layout_is_wrapped (self->layout) && + !pango_layout_is_ellipsized (self->layout)) + { + PangoRectangle rect; - self->mnemonic_keyval = GDK_KEY_VoidSymbol; - self->layout = NULL; - self->text = g_strdup (""); - self->attrs = NULL; + if (width == -1) + return g_object_ref (self->layout); - self->mnemonic_widget = NULL; + pango_layout_get_extents (self->layout, NULL, &rect); + if (rect.width <= width) + return g_object_ref (self->layout); + } - self->mnemonics_visible = FALSE; + copy = pango_layout_copy (self->layout); + pango_layout_set_width (copy, width); + return copy; } - static void -gtk_label_buildable_interface_init (GtkBuildableIface *iface) -{ - buildable_parent_iface = g_type_interface_peek_parent (iface); - - iface->custom_tag_start = gtk_label_buildable_custom_tag_start; - iface->custom_finished = gtk_label_buildable_custom_finished; -} - -static const GtkBuildableParser pango_parser = -{ - gtk_pango_attribute_start_element, -}; - -static gboolean -gtk_label_buildable_custom_tag_start (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *child, - const char *tagname, - GtkBuildableParser *parser, - gpointer *data) +get_height_for_width (GtkLabel *self, + int width, + int *minimum_height, + int *natural_height, + int *minimum_baseline, + int *natural_baseline) { - if (buildable_parent_iface->custom_tag_start (buildable, builder, child, - tagname, parser, data)) - return TRUE; - - if (strcmp (tagname, "attributes") == 0) - { - GtkPangoAttributeParserData *parser_data; + PangoLayout *layout; + int text_height, baseline; - parser_data = g_slice_new0 (GtkPangoAttributeParserData); - parser_data->builder = g_object_ref (builder); - parser_data->object = (GObject *) g_object_ref (buildable); - *parser = pango_parser; - *data = parser_data; - return TRUE; - } - return FALSE; -} + layout = gtk_label_get_measuring_layout (self, NULL, width * PANGO_SCALE); -static void -gtk_label_buildable_custom_finished (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *child, - const char *tagname, - gpointer user_data) -{ - GtkPangoAttributeParserData *data = user_data; + pango_layout_get_pixel_size (layout, NULL, &text_height); - buildable_parent_iface->custom_finished (buildable, builder, child, - tagname, user_data); + *minimum_height = text_height; + *natural_height = text_height; - if (strcmp (tagname, "attributes") == 0) - { - if (data->attrs) - { - gtk_label_set_attributes (GTK_LABEL (buildable), data->attrs); - pango_attr_list_unref (data->attrs); - } + baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; + *minimum_baseline = baseline; + *natural_baseline = baseline; - g_object_unref (data->object); - g_object_unref (data->builder); - g_slice_free (GtkPangoAttributeParserData, data); - } + g_object_unref (layout); } - -/** - * gtk_label_new: - * @str: (nullable): The text of the label - * - * Creates a new label with the given text inside it. You can - * pass %NULL to get an empty label widget. - * - * Returns: the new #GtkLabel - **/ -GtkWidget* -gtk_label_new (const char *str) +static int +get_char_pixels (GtkWidget *self, + PangoLayout *layout) { - GtkLabel *self; - - self = g_object_new (GTK_TYPE_LABEL, NULL); + PangoContext *context; + PangoFontMetrics *metrics; + int char_width, digit_width; - if (str && *str) - gtk_label_set_text (self, str); + context = pango_layout_get_context (layout); + metrics = pango_context_get_metrics (context, + pango_context_get_font_description (context), + pango_context_get_language (context)); + char_width = pango_font_metrics_get_approximate_char_width (metrics); + digit_width = pango_font_metrics_get_approximate_digit_width (metrics); + pango_font_metrics_unref (metrics); - return GTK_WIDGET (self); + return MAX (char_width, digit_width);; } -/** - * gtk_label_new_with_mnemonic: - * @str: (nullable): The text of the label, with an underscore in front of the - * mnemonic character - * - * Creates a new #GtkLabel, containing the text in @str. - * - * If characters in @str are preceded by an underscore, they are - * underlined. If you need a literal underscore character in a label, use - * '__' (two underscores). The first underlined character represents a - * keyboard accelerator called a mnemonic. The mnemonic key can be used - * to activate another widget, chosen automatically, or explicitly using - * gtk_label_set_mnemonic_widget(). - * - * If gtk_label_set_mnemonic_widget() is not called, then the first - * activatable ancestor of the #GtkLabel will be chosen as the mnemonic - * widget. For instance, if the label is inside a button or menu item, - * the button or menu item will automatically become the mnemonic widget - * and be activated by the mnemonic. - * - * Returns: the new #GtkLabel - **/ -GtkWidget* -gtk_label_new_with_mnemonic (const char *str) +static void +gtk_label_get_preferred_layout_size (GtkLabel *self, + PangoRectangle *smallest, + PangoRectangle *widest, + int *smallest_baseline, + int *widest_baseline) { - GtkLabel *self; - - self = g_object_new (GTK_TYPE_LABEL, NULL); - - if (str && *str) - gtk_label_set_text_with_mnemonic (self, str); + PangoLayout *layout; + int char_pixels; - return GTK_WIDGET (self); -} + /* "width-chars" Hard-coded minimum width: + * - minimum size should be MAX (width-chars, strlen ("...")); + * - natural size should be MAX (width-chars, strlen (self->text)); + * + * "max-width-chars" User specified maximum size requisition + * - minimum size should be MAX (width-chars, 0) + * - natural size should be MIN (max-width-chars, strlen (self->text)) + * + * For ellipsizing labels; if max-width-chars is specified: either it is used as + * a minimum size or the label text as a minimum size (natural size still overflows). + * + * For wrapping labels; A reasonable minimum size is useful to naturally layout + * interfaces automatically. In this case if no "width-chars" is specified, the minimum + * width will default to the wrap guess that gtk_label_ensure_layout() does. + */ -static gboolean -gtk_label_mnemonic_activate (GtkWidget *widget, - gboolean group_cycling) -{ - GtkLabel *self = GTK_LABEL (widget); - GtkWidget *parent; + /* Start off with the pixel extents of an as-wide-as-possible layout */ + layout = gtk_label_get_measuring_layout (self, NULL, -1); - if (self->mnemonic_widget) - return gtk_widget_mnemonic_activate (self->mnemonic_widget, group_cycling); + if (self->width_chars > -1 || self->max_width_chars > -1) + char_pixels = get_char_pixels (GTK_WIDGET (self), layout); + else + char_pixels = 0; - /* Try to find the widget to activate by traversing the - * widget's ancestry. - */ - parent = gtk_widget_get_parent (widget); + pango_layout_get_extents (layout, NULL, widest); + widest->width = MAX (widest->width, char_pixels * self->width_chars); + widest->x = widest->y = 0; + *widest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - if (GTK_IS_NOTEBOOK (parent)) - return FALSE; - - while (parent) + if (self->ellipsize || self->wrap) { - if (gtk_widget_get_can_focus (parent) || - (!group_cycling && gtk_widget_can_activate (parent)) || - GTK_IS_NOTEBOOK (gtk_widget_get_parent (parent))) - return gtk_widget_mnemonic_activate (parent, group_cycling); - parent = gtk_widget_get_parent (parent); - } - - /* barf if there was nothing to activate */ - g_warning ("Couldn't find a target for a mnemonic activation."); - gtk_widget_error_bell (widget); + /* a layout with width 0 will be as small as humanly possible */ + layout = gtk_label_get_measuring_layout (self, + layout, + self->width_chars > -1 ? char_pixels * self->width_chars + : 0); - return FALSE; -} + pango_layout_get_extents (layout, NULL, smallest); + smallest->width = MAX (smallest->width, char_pixels * self->width_chars); + smallest->x = smallest->y = 0; -static void -label_mnemonics_visible_changed (GtkWidget *widget, - GParamSpec *pspec, - gpointer data) -{ - gboolean visible; + *smallest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - g_object_get (widget, "mnemonics-visible", &visible, NULL); - _gtk_label_mnemonics_visible_apply_recursively (widget, visible); -} + if (self->max_width_chars > -1 && widest->width > char_pixels * self->max_width_chars) + { + layout = gtk_label_get_measuring_layout (self, + layout, + MAX (smallest->width, char_pixels * self->max_width_chars)); + pango_layout_get_extents (layout, NULL, widest); + widest->width = MAX (widest->width, char_pixels * self->width_chars); + widest->x = widest->y = 0; -static void -gtk_label_setup_mnemonic (GtkLabel *self) -{ - GtkWidget *widget = GTK_WIDGET (self); - GtkShortcut *shortcut; - GtkNative *native; - gboolean connected; - gboolean mnemonics_visible; + *widest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; + } - if (self->mnemonic_keyval == GDK_KEY_VoidSymbol) - { - if (self->mnemonic_controller) + if (widest->width < smallest->width) { - gtk_widget_remove_controller (widget, self->mnemonic_controller); - self->mnemonic_controller = NULL; + *smallest = *widest; + *smallest_baseline = *widest_baseline; } - return; - } - - if (self->mnemonic_controller == NULL) - { - self->mnemonic_controller = gtk_shortcut_controller_new (); - gtk_event_controller_set_propagation_phase (self->mnemonic_controller, GTK_PHASE_CAPTURE); - gtk_shortcut_controller_set_scope (GTK_SHORTCUT_CONTROLLER (self->mnemonic_controller), GTK_SHORTCUT_SCOPE_MANAGED); - shortcut = gtk_shortcut_new (gtk_mnemonic_trigger_new (self->mnemonic_keyval), - g_object_ref (gtk_mnemonic_action_get ())); - gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (self->mnemonic_controller), shortcut); - gtk_widget_add_controller (GTK_WIDGET (self), self->mnemonic_controller); } else { - shortcut = g_list_model_get_item (G_LIST_MODEL (self->mnemonic_controller), 0); - gtk_shortcut_set_trigger (shortcut, gtk_mnemonic_trigger_new (self->mnemonic_keyval)); - g_object_unref (shortcut); + *smallest = *widest; + *smallest_baseline = *widest_baseline; } - /* Connect to notify::mnemonics-visible of the root */ - native = gtk_widget_get_native (GTK_WIDGET (self)); - if (!GTK_IS_WINDOW (native) && !GTK_IS_POPOVER (native)) - return; - - /* always set up this widgets initial value */ - g_object_get (native, "mnemonics-visible", &mnemonics_visible, NULL); - self->mnemonics_visible = mnemonics_visible; - - connected = - GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (native), quark_mnemonics_visible_connected)); - - if (!connected) - { - g_signal_connect (native, - "notify::mnemonics-visible", - G_CALLBACK (label_mnemonics_visible_changed), - self); - g_object_set_qdata (G_OBJECT (native), - quark_mnemonics_visible_connected, - GINT_TO_POINTER (1)); - } + g_object_unref (layout); } static void -gtk_label_root (GtkWidget *widget) -{ - GtkLabel *self = GTK_LABEL (widget); - - GTK_WIDGET_CLASS (gtk_label_parent_class)->root (widget); +gtk_label_get_preferred_size (GtkWidget *widget, + GtkOrientation orientation, + int *minimum_size, + int *natural_size, + int *minimum_baseline, + int *natural_baseline) +{ + GtkLabel *self = GTK_LABEL (widget); + PangoRectangle widest_rect; + PangoRectangle smallest_rect; + int smallest_baseline; + int widest_baseline; - gtk_label_setup_mnemonic (self); + gtk_label_get_preferred_layout_size (self, + &smallest_rect, &widest_rect, + &smallest_baseline, &widest_baseline); - /* The PangoContext is replaced when the display changes, so clear the layouts */ - gtk_label_clear_layout (GTK_LABEL (widget)); -} + widest_rect.width = PANGO_PIXELS_CEIL (widest_rect.width); + widest_rect.height = PANGO_PIXELS_CEIL (widest_rect.height); -static void -gtk_label_unroot (GtkWidget *widget) -{ - GtkLabel *self = GTK_LABEL (widget); + smallest_rect.width = PANGO_PIXELS_CEIL (smallest_rect.width); + smallest_rect.height = PANGO_PIXELS_CEIL (smallest_rect.height); - gtk_label_setup_mnemonic (self); + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + /* Normal desired width */ + *minimum_size = smallest_rect.width; + *natural_size = widest_rect.width; - GTK_WIDGET_CLASS (gtk_label_parent_class)->unroot (widget); -} + if (minimum_baseline) + *minimum_baseline = -1; -void -_gtk_label_mnemonics_visible_apply_recursively (GtkWidget *widget, - gboolean visible) -{ - if (GTK_IS_LABEL (widget)) + if (natural_baseline) + *natural_baseline = -1; + } + else /* GTK_ORIENTATION_VERTICAL */ { - GtkLabel *self = GTK_LABEL (widget); - - if (self->mnemonics_visible != visible) + if (smallest_rect.height < widest_rect.height) { - self->mnemonics_visible = visible; - gtk_label_recalculate (self); + *minimum_size = smallest_rect.height; + *natural_size = widest_rect.height; + if (minimum_baseline) + *minimum_baseline = smallest_baseline; + if (natural_baseline) + *natural_baseline = widest_baseline; } - } - else - { - GtkWidget *child; - - for (child = gtk_widget_get_first_child (widget); - child; - child = gtk_widget_get_next_sibling (child)) + else { - if (GTK_IS_NATIVE (child)) - continue; - - _gtk_label_mnemonics_visible_apply_recursively (child, visible); + *minimum_size = widest_rect.height; + *natural_size = smallest_rect.height; + if (minimum_baseline) + *minimum_baseline = widest_baseline; + if (natural_baseline) + *natural_baseline = smallest_baseline; } } } + + static void -label_mnemonic_widget_weak_notify (gpointer data, - GObject *where_the_object_was) +gtk_label_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) { - GtkLabel *self = data; + GtkLabel *self = GTK_LABEL (widget); - self->mnemonic_widget = NULL; - g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_MNEMONIC_WIDGET]); + if (orientation == GTK_ORIENTATION_VERTICAL && for_size != -1 && self->wrap) + { + gtk_label_clear_layout (self); + + get_height_for_width (self, for_size, minimum, natural, minimum_baseline, natural_baseline); + } + else + gtk_label_get_preferred_size (widget, orientation, minimum, natural, minimum_baseline, natural_baseline); } -/** - * gtk_label_set_mnemonic_widget: - * @self: a #GtkLabel - * @widget: (nullable): the target #GtkWidget, or %NULL to unset - * - * If the label has been set so that it has a mnemonic key (using - * i.e. gtk_label_set_markup_with_mnemonic(), - * gtk_label_set_text_with_mnemonic(), gtk_label_new_with_mnemonic() - * or the “use_underline” property) the label can be associated with a - * widget that is the target of the mnemonic. When the label is inside - * a widget (like a #GtkButton or a #GtkNotebook tab) it is - * automatically associated with the correct widget, but sometimes - * (i.e. when the target is a #GtkEntry next to the label) you need to - * set it explicitly using this function. - * - * The target widget will be accelerated by emitting the - * GtkWidget::mnemonic-activate signal on it. The default handler for - * this signal will activate the widget if there are no mnemonic collisions - * and toggle focus between the colliding widgets otherwise. - **/ -void -gtk_label_set_mnemonic_widget (GtkLabel *self, - GtkWidget *widget) +static void +get_layout_location (GtkLabel *self, + int *xp, + int *yp) { - g_return_if_fail (GTK_IS_LABEL (self)); + GtkWidget *widget = GTK_WIDGET (self); + int layout_width, layout_height, x, y; + float xalign, yalign; + PangoRectangle logical; + int baseline, layout_baseline, baseline_offset; + int widget_width, widget_height; - if (widget) - g_return_if_fail (GTK_IS_WIDGET (widget)); + xalign = self->xalign; + yalign = self->yalign; - if (self->mnemonic_widget) - { - gtk_widget_remove_mnemonic_label (self->mnemonic_widget, GTK_WIDGET (self)); - g_object_weak_unref (G_OBJECT (self->mnemonic_widget), - label_mnemonic_widget_weak_notify, - self); - } - self->mnemonic_widget = widget; - if (self->mnemonic_widget) - { - g_object_weak_ref (G_OBJECT (self->mnemonic_widget), - label_mnemonic_widget_weak_notify, - self); - gtk_widget_add_mnemonic_label (self->mnemonic_widget, GTK_WIDGET (self)); - } + if (_gtk_widget_get_direction (widget) != GTK_TEXT_DIR_LTR) + xalign = 1.0 - xalign; - g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_MNEMONIC_WIDGET]); -} + pango_layout_get_pixel_extents (self->layout, NULL, &logical); -/** - * gtk_label_get_mnemonic_widget: - * @self: a #GtkLabel - * - * Retrieves the target of the mnemonic (keyboard shortcut) of this - * label. See gtk_label_set_mnemonic_widget(). - * - * Returns: (nullable) (transfer none): the target of the label’s mnemonic, - * or %NULL if none has been set and the default algorithm will be used. - **/ -GtkWidget * -gtk_label_get_mnemonic_widget (GtkLabel *self) -{ - g_return_val_if_fail (GTK_IS_LABEL (self), NULL); + layout_width = logical.width; + layout_height = logical.height; - return self->mnemonic_widget; -} + widget_width = gtk_widget_get_width (widget); + widget_height = gtk_widget_get_height (widget); -/** - * gtk_label_get_mnemonic_keyval: - * @self: a #GtkLabel - * - * If the label has been set so that it has a mnemonic key this function - * returns the keyval used for the mnemonic accelerator. If there is no - * mnemonic set up it returns #GDK_KEY_VoidSymbol. - * - * Returns: GDK keyval usable for accelerators, or #GDK_KEY_VoidSymbol - **/ -guint -gtk_label_get_mnemonic_keyval (GtkLabel *self) -{ - g_return_val_if_fail (GTK_IS_LABEL (self), GDK_KEY_VoidSymbol); + baseline = gtk_widget_get_allocated_baseline (widget); - return self->mnemonic_keyval; -} + x = floor ((xalign * (widget_width - layout_width)) - logical.x); -static void -gtk_label_set_text_internal (GtkLabel *self, - char *str) -{ - if (g_strcmp0 (self->text, str) == 0) + baseline_offset = 0; + if (baseline != -1) { - g_free (str); - return; + layout_baseline = pango_layout_get_baseline (self->layout) / PANGO_SCALE; + baseline_offset = baseline - layout_baseline; + yalign = 0.0; /* Can't support yalign while baseline aligning */ } - g_free (self->text); - self->text = str; + y = floor ((widget_height - layout_height) * yalign) + baseline_offset; - gtk_accessible_update_property (GTK_ACCESSIBLE (self), - GTK_ACCESSIBLE_PROPERTY_LABEL, self->text, - -1); + if (xp) + *xp = x; - gtk_label_select_region_index (self, 0, 0); + if (yp) + *yp = y; } -static gboolean -gtk_label_set_label_internal (GtkLabel *self, - const char *str) +static void +gtk_label_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) { - if (g_strcmp0 (str, self->label) == 0) - return FALSE; - - g_free (self->label); - self->label = g_strdup (str ? str : ""); + GtkLabel *self = GTK_LABEL (widget); - g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_LABEL]); + if (self->layout) + { + if (self->ellipsize || self->wrap) + pango_layout_set_width (self->layout, width * PANGO_SCALE); + else + pango_layout_set_width (self->layout, -1); + } - return TRUE; + if (self->popup_menu) + gtk_popover_present (GTK_POPOVER (self->popup_menu)); } -static gboolean -gtk_label_set_use_markup_internal (GtkLabel *self, - gboolean val) -{ - if (self->use_markup != val) - { - self->use_markup = val; - - g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_USE_MARKUP]); - return TRUE; - } - return FALSE; -} +#define GRAPHENE_RECT_FROM_RECT(_r) (GRAPHENE_RECT_INIT ((_r)->x, (_r)->y, (_r)->width, (_r)->height)) -static gboolean -gtk_label_set_use_underline_internal (GtkLabel *self, - gboolean val) +static void +gtk_label_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) { - if (self->use_underline != val) - { - self->use_underline = val; + GtkLabel *self = GTK_LABEL (widget); + GtkLabelSelectionInfo *info; + GtkStyleContext *context; + int lx, ly; + int width, height; - g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_USE_UNDERLINE]); + if (!self->text || (*self->text == '\0')) + return; - return TRUE; - } + gtk_label_ensure_layout (self); - return FALSE; -} + context = _gtk_widget_get_style_context (widget); + get_layout_location (self, &lx, &ly); -/* Calculates text, attrs and mnemonic_keyval from - * label, use_underline and use_markup - */ -static void -gtk_label_recalculate (GtkLabel *self) -{ - guint keyval = self->mnemonic_keyval; + gtk_snapshot_render_layout (snapshot, context, lx, ly, self->layout); - gtk_label_clear_links (self); - gtk_label_clear_layout (self); - gtk_label_clear_select_info (self); + info = self->select_info; + if (!info) + return; - if (self->use_markup || self->use_underline) - gtk_label_set_markup_internal (self, self->label, self->use_underline); - else + width = gtk_widget_get_width (widget); + height = gtk_widget_get_height (widget); + + if (info->selection_anchor != info->selection_end) { - g_clear_pointer (&self->markup_attrs, pango_attr_list_unref); + int range[2]; + cairo_region_t *range_clip; + cairo_rectangle_int_t clip_rect; + int i; - gtk_label_set_text_internal (self, g_strdup (self->label)); - } + range[0] = MIN (info->selection_anchor, info->selection_end); + range[1] = MAX (info->selection_anchor, info->selection_end); - if (!self->use_underline) - self->mnemonic_keyval = GDK_KEY_VoidSymbol; + gtk_style_context_save_to_node (context, info->selection_node); - if (keyval != self->mnemonic_keyval) - { - gtk_label_setup_mnemonic (self); - g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_MNEMONIC_KEYVAL]); + range_clip = gdk_pango_layout_get_clip_region (self->layout, lx, ly, range, 1); + for (i = 0; i < cairo_region_num_rectangles (range_clip); i++) + { + cairo_region_get_rectangle (range_clip, i, &clip_rect); + + gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_FROM_RECT (&clip_rect)); + gtk_snapshot_render_background (snapshot, context, 0, 0, width, height); + gtk_snapshot_render_layout (snapshot, context, lx, ly, self->layout); + gtk_snapshot_pop (snapshot); + } + + cairo_region_destroy (range_clip); + + gtk_style_context_restore (context); } + else + { + GtkLabelLink *focus_link; + GtkLabelLink *active_link; + int range[2]; + cairo_region_t *range_clip; + cairo_rectangle_int_t clip_rect; + int i; + GdkRectangle rect; - gtk_widget_queue_resize (GTK_WIDGET (self)); -} + if (info->selectable && + gtk_widget_has_focus (widget) && + gtk_widget_is_drawable (widget)) + { + PangoDirection cursor_direction; -/** - * gtk_label_set_text: - * @self: a #GtkLabel - * @str: The text you want to set - * - * Sets the text within the #GtkLabel widget. It overwrites any text that - * was there before. - * - * This function will clear any previously set mnemonic accelerators, and - * set the #GtkLabel:use-underline property to %FALSE as a side effect. - * - * This function will set the #GtkLabel:use-markup property to %FALSE - * as a side effect. - * - * See also: gtk_label_set_markup() - **/ -void -gtk_label_set_text (GtkLabel *self, - const char *str) -{ - gboolean changed; + cursor_direction = get_cursor_direction (self); + gtk_snapshot_render_insertion_cursor (snapshot, context, + lx, ly, + self->layout, self->select_info->selection_end, + cursor_direction); + } - g_return_if_fail (GTK_IS_LABEL (self)); + focus_link = gtk_label_get_focus_link (self, NULL); + active_link = info->active_link; - g_object_freeze_notify (G_OBJECT (self)); + if (active_link) + { + range[0] = active_link->start; + range[1] = active_link->end; - changed = gtk_label_set_label_internal (self, str); - changed = gtk_label_set_use_markup_internal (self, FALSE) || changed; - changed = gtk_label_set_use_underline_internal (self, FALSE) || changed; + gtk_style_context_save_to_node (context, active_link->cssnode); - if (changed) - gtk_label_recalculate (self); + range_clip = gdk_pango_layout_get_clip_region (self->layout, lx, ly, range, 1); + for (i = 0; i < cairo_region_num_rectangles (range_clip); i++) + { + cairo_region_get_rectangle (range_clip, i, &clip_rect); - g_object_thaw_notify (G_OBJECT (self)); -} + gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_FROM_RECT (&clip_rect)); + gtk_snapshot_render_background (snapshot, context, 0, 0, width, height); + gtk_snapshot_render_layout (snapshot, context, lx, ly, self->layout); + gtk_snapshot_pop (snapshot); + } -/** - * gtk_label_set_attributes: - * @self: a #GtkLabel - * @attrs: (nullable): a #PangoAttrList, or %NULL - * - * Sets a #PangoAttrList; the attributes in the list are applied to the - * label text. - * - * The attributes set with this function will be applied - * and merged with any other attributes previously effected by way - * of the #GtkLabel:use-underline or #GtkLabel:use-markup properties. - * While it is not recommended to mix markup strings with manually set - * attributes, if you must; know that the attributes will be applied - * to the label after the markup string is parsed. - **/ -void -gtk_label_set_attributes (GtkLabel *self, - PangoAttrList *attrs) -{ - g_return_if_fail (GTK_IS_LABEL (self)); + cairo_region_destroy (range_clip); - if (!attrs && !self->attrs) - return; + gtk_style_context_restore (context); + } - if (attrs) - pango_attr_list_ref (attrs); + if (focus_link && gtk_widget_has_visible_focus (widget)) + { + range[0] = focus_link->start; + range[1] = focus_link->end; - if (self->attrs) - pango_attr_list_unref (self->attrs); - self->attrs = attrs; + gtk_style_context_save_to_node (context, focus_link->cssnode); - g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_ATTRIBUTES]); + range_clip = gdk_pango_layout_get_clip_region (self->layout, lx, ly, range, 1); + cairo_region_get_extents (range_clip, &rect); - gtk_label_clear_layout (self); - gtk_widget_queue_resize (GTK_WIDGET (self)); -} + gtk_snapshot_render_focus (snapshot, context, rect.x, rect.y, rect.width, rect.height); -/** - * gtk_label_get_attributes: - * @self: a #GtkLabel - * - * Gets the attribute list that was set on the label using - * gtk_label_set_attributes(), if any. This function does - * not reflect attributes that come from the labels markup - * (see gtk_label_set_markup()). If you want to get the - * effective attributes for the label, use - * pango_layout_get_attribute (gtk_label_get_layout (self)). - * - * Returns: (nullable) (transfer none): the attribute list, or %NULL - * if none was set. - **/ -PangoAttrList * -gtk_label_get_attributes (GtkLabel *self) -{ - g_return_val_if_fail (GTK_IS_LABEL (self), NULL); + cairo_region_destroy (range_clip); - return self->attrs; + gtk_style_context_restore (context); + } + } } -/** - * gtk_label_set_label: - * @self: a #GtkLabel - * @str: the new text to set for the label - * - * Sets the text of the label. The label is interpreted as - * including embedded underlines and/or Pango markup depending - * on the values of the #GtkLabel:use-underline and - * #GtkLabel:use-markup properties. - **/ -void -gtk_label_set_label (GtkLabel *self, - const char *str) +static GtkSizeRequestMode +gtk_label_get_request_mode (GtkWidget *widget) { - g_return_if_fail (GTK_IS_LABEL (self)); - - g_object_freeze_notify (G_OBJECT (self)); + GtkLabel *self = GTK_LABEL (widget); - if (gtk_label_set_label_internal (self, str)) - gtk_label_recalculate (self); + if (self->wrap) + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; - g_object_thaw_notify (G_OBJECT (self)); + return GTK_SIZE_REQUEST_CONSTANT_SIZE; } -/** - * gtk_label_get_label: - * @self: a #GtkLabel - * - * Fetches the text from a label widget including any embedded - * underlines indicating mnemonics and Pango markup. (See - * gtk_label_get_text()). - * - * Returns: the text of the label widget. This string is - * owned by the widget and must not be modified or freed. - **/ -const char * -gtk_label_get_label (GtkLabel *self) +static void +gtk_label_dispose (GObject *object) { - g_return_val_if_fail (GTK_IS_LABEL (self), NULL); + GtkLabel *self = GTK_LABEL (object); - return self->label; + gtk_label_set_mnemonic_widget (self, NULL); + + G_OBJECT_CLASS (gtk_label_parent_class)->dispose (object); } -typedef struct +static void +gtk_label_clear_links (GtkLabel *self) { - GtkLabel *label; - GArray *links; - GString *new_str; - gsize text_len; -} UriParserData; + guint i; -static void -start_element_handler (GMarkupParseContext *context, - const char *element_name, - const char **attribute_names, - const char **attribute_values, - gpointer user_data, - GError **error) -{ - UriParserData *pdata = user_data; - GtkLabel *self = pdata->label; + if (!self->select_info) + return; - if (strcmp (element_name, "a") == 0) + for (i = 0; i < self->select_info->n_links; i++) { - GtkLabelLink link; - const char *uri = NULL; - const char *title = NULL; - const char *class = NULL; - gboolean visited = FALSE; - int line_number; - int char_number; - int i; - GtkCssNode *widget_node; - GtkStateFlags state; - - g_markup_parse_context_get_position (context, &line_number, &char_number); + const GtkLabelLink *link = &self->select_info->links[i]; + gtk_css_node_set_parent (link->cssnode, NULL); + g_free (link->uri); + g_free (link->title); + } + g_free (self->select_info->links); + self->select_info->links = NULL; + self->select_info->n_links = 0; + self->select_info->active_link = NULL; + gtk_widget_remove_css_class (GTK_WIDGET (self), "link"); +} - for (i = 0; attribute_names[i] != NULL; i++) - { - const char *attr = attribute_names[i]; +static void +gtk_label_finalize (GObject *object) +{ + GtkLabel *self = GTK_LABEL (object); - if (strcmp (attr, "href") == 0) - uri = attribute_values[i]; - else if (strcmp (attr, "title") == 0) - title = attribute_values[i]; - else if (strcmp (attr, "class") == 0) - class = attribute_values[i]; - else - { - g_set_error (error, - G_MARKUP_ERROR, - G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, - "Attribute '%s' is not allowed on the tag " - "on line %d char %d", - attr, line_number, char_number); - return; - } - } + g_free (self->label); + g_free (self->text); - if (uri == NULL) - { - g_set_error (error, - G_MARKUP_ERROR, - G_MARKUP_ERROR_INVALID_CONTENT, - "Attribute 'href' was missing on the tag " - "on line %d char %d", - line_number, char_number); - return; - } + g_clear_object (&self->layout); + g_clear_pointer (&self->attrs, pango_attr_list_unref); + g_clear_pointer (&self->markup_attrs, pango_attr_list_unref); - visited = FALSE; - if (self->select_info) - { - for (i = 0; i < self->select_info->n_links; i++) - { - const GtkLabelLink *l = &self->select_info->links[i]; + if (self->select_info) + g_object_unref (self->select_info->provider); - if (strcmp (uri, l->uri) == 0) - { - visited = l->visited; - break; - } - } - } + gtk_label_clear_links (self); + g_free (self->select_info); - if (!pdata->links) - pdata->links = g_array_new (FALSE, TRUE, sizeof (GtkLabelLink)); + g_clear_pointer (&self->popup_menu, gtk_widget_unparent); + g_clear_object (&self->extra_menu); - link.uri = g_strdup (uri); - link.title = g_strdup (title); + G_OBJECT_CLASS (gtk_label_parent_class)->finalize (object); +} - widget_node = gtk_widget_get_css_node (GTK_WIDGET (pdata->label)); - link.cssnode = gtk_css_node_new (); - gtk_css_node_set_name (link.cssnode, g_quark_from_static_string ("link")); - gtk_css_node_set_parent (link.cssnode, widget_node); - if (class) - gtk_css_node_add_class (link.cssnode, g_quark_from_string (class)); +static void +gtk_label_unrealize (GtkWidget *widget) +{ + GtkLabel *self = GTK_LABEL (widget); - state = gtk_css_node_get_state (widget_node); - if (visited) - state |= GTK_STATE_FLAG_VISITED; - else - state |= GTK_STATE_FLAG_LINK; - gtk_css_node_set_state (link.cssnode, state); - g_object_unref (link.cssnode); + if (self->select_info && + self->select_info->provider) + { + GdkClipboard *clipboard = gtk_widget_get_primary_clipboard (widget); - link.visited = visited; - link.start = pdata->text_len; - g_array_append_val (pdata->links, link); + if (gdk_clipboard_get_content (clipboard) == self->select_info->provider) + gdk_clipboard_set_content (clipboard, NULL); } - else - { - int i; - g_string_append_c (pdata->new_str, '<'); - g_string_append (pdata->new_str, element_name); + GTK_WIDGET_CLASS (gtk_label_parent_class)->unrealize (widget); +} - for (i = 0; attribute_names[i] != NULL; i++) - { - const char *attr = attribute_names[i]; - const char *value = attribute_values[i]; - char *newvalue; +static gboolean +range_is_in_ellipsis_full (GtkLabel *self, + int range_start, + int range_end, + int *ellipsis_start, + int *ellipsis_end) +{ + PangoLayoutIter *iter; + gboolean in_ellipsis; - newvalue = g_markup_escape_text (value, -1); + if (!self->ellipsize) + return FALSE; - g_string_append_c (pdata->new_str, ' '); - g_string_append (pdata->new_str, attr); - g_string_append (pdata->new_str, "=\""); - g_string_append (pdata->new_str, newvalue); - g_string_append_c (pdata->new_str, '\"'); + gtk_label_ensure_layout (self); - g_free (newvalue); - } - g_string_append_c (pdata->new_str, '>'); - } -} + if (!pango_layout_is_ellipsized (self->layout)) + return FALSE; -static void -end_element_handler (GMarkupParseContext *context, - const char *element_name, - gpointer user_data, - GError **error) -{ - UriParserData *pdata = user_data; + iter = pango_layout_get_iter (self->layout); - if (!strcmp (element_name, "a")) - { - GtkLabelLink *link = &g_array_index (pdata->links, GtkLabelLink, pdata->links->len - 1); - link->end = pdata->text_len; - } - else - { - g_string_append (pdata->new_str, "new_str, element_name); - g_string_append_c (pdata->new_str, '>'); - } -} + in_ellipsis = FALSE; -static void -text_handler (GMarkupParseContext *context, - const char *text, - gsize text_len, - gpointer user_data, - GError **error) -{ - UriParserData *pdata = user_data; - char *newtext; + do { + PangoLayoutRun *run; - newtext = g_markup_escape_text (text, text_len); - g_string_append (pdata->new_str, newtext); - pdata->text_len += text_len; - g_free (newtext); -} + run = pango_layout_iter_get_run_readonly (iter); + if (run) + { + PangoItem *item; -static const GMarkupParser markup_parser = -{ - start_element_handler, - end_element_handler, - text_handler, - NULL, - NULL -}; + item = ((PangoGlyphItem*)run)->item; -static gboolean -xml_isspace (char c) -{ - return (c == ' ' || c == '\t' || c == '\n' || c == '\r'); + if (item->offset <= range_start && range_end <= item->offset + item->length) + { + if (item->analysis.flags & PANGO_ANALYSIS_FLAG_IS_ELLIPSIS) + { + if (ellipsis_start) + *ellipsis_start = item->offset; + if (ellipsis_end) + *ellipsis_end = item->offset + item->length; + in_ellipsis = TRUE; + } + break; + } + else if (item->offset + item->length >= range_end) + break; + } + } while (pango_layout_iter_next_run (iter)); + + pango_layout_iter_free (iter); + + return in_ellipsis; } -static void -link_free (GtkLabelLink *link) +static gboolean +range_is_in_ellipsis (GtkLabel *self, + int range_start, + int range_end) { - gtk_css_node_set_parent (link->cssnode, NULL); - g_free (link->uri); - g_free (link->title); + return range_is_in_ellipsis_full (self, range_start, range_end, NULL, NULL); } static gboolean -parse_uri_markup (GtkLabel *self, - const char *str, - char **new_str, - GtkLabelLink **links, - guint *out_n_links, - GError **error) +gtk_label_grab_focus (GtkWidget *widget) { - GMarkupParseContext *context; - const char *p, *end; - gsize length; - UriParserData pdata; - - length = strlen (str); - p = str; - end = str + length; + GtkLabel *self = GTK_LABEL (widget); + gboolean select_on_focus; + GtkWidget *prev_focus; - pdata.label = self; - pdata.links = NULL; - pdata.new_str = g_string_sized_new (length); - pdata.text_len = 0; + if (self->select_info == NULL) + return FALSE; - while (p != end && xml_isspace (*p)) - p++; + prev_focus = gtk_root_get_focus (gtk_widget_get_root (widget)); - context = g_markup_parse_context_new (&markup_parser, 0, &pdata, NULL); + if (!GTK_WIDGET_CLASS (gtk_label_parent_class)->grab_focus (widget)) + return FALSE; - if (end - p >= 8 && strncmp (p, "", 8) == 0) + if (self->select_info->selectable) { - if (!g_markup_parse_context_parse (context, str, length, error)) - goto failed; + g_object_get (gtk_widget_get_settings (widget), + "gtk-label-select-on-focus", + &select_on_focus, + NULL); + + if (select_on_focus && !self->in_click && + !(prev_focus && gtk_widget_is_ancestor (prev_focus, widget))) + gtk_label_select_region (self, 0, -1); } else { - if (!g_markup_parse_context_parse (context, "", 8, error)) - goto failed; + if (self->select_info->links && !self->in_click && + !(prev_focus && gtk_widget_is_ancestor (prev_focus, widget))) + { + guint i; - if (!g_markup_parse_context_parse (context, str, length, error)) - goto failed; + for (i = 0; i < self->select_info->n_links; i++) + { + const GtkLabelLink *link = &self->select_info->links[i]; - if (!g_markup_parse_context_parse (context, "", 9, error)) - goto failed; + if (!range_is_in_ellipsis (self, link->start, link->end)) + { + self->select_info->selection_anchor = link->start; + self->select_info->selection_end = link->start; + break; + } + } + } } - if (!g_markup_parse_context_end_parse (context, error)) - goto failed; + return TRUE; +} - g_markup_parse_context_free (context); +static gboolean +get_layout_index (GtkLabel *self, + int x, + int y, + int *index) +{ + int trailing = 0; + const char *cluster; + const char *cluster_end; + gboolean inside; + int lx, ly; - *new_str = g_string_free (pdata.new_str, FALSE); + *index = 0; - if (pdata.links) - { - *out_n_links = pdata.links->len; - *links = (GtkLabelLink *)g_array_free (pdata.links, FALSE); - } - else - { - *links = NULL; - } + gtk_label_ensure_layout (self); + get_layout_location (self, &lx, &ly); - return TRUE; + /* Translate x/y to layout position */ + x -= lx; + y -= ly; -failed: - g_markup_parse_context_free (context); - g_string_free (pdata.new_str, TRUE); + x *= PANGO_SCALE; + y *= PANGO_SCALE; - if (pdata.links) - g_array_free (pdata.links, TRUE); + inside = pango_layout_xy_to_index (self->layout, + x, y, + index, &trailing); - return FALSE; + cluster = self->text + *index; + cluster_end = cluster; + while (trailing) + { + cluster_end = g_utf8_next_char (cluster_end); + --trailing; + } + + *index += (cluster_end - cluster); + + return inside; } -static void -gtk_label_ensure_has_tooltip (GtkLabel *self) +static gboolean +gtk_label_query_tooltip (GtkWidget *widget, + int x, + int y, + gboolean keyboard_tip, + GtkTooltip *tooltip) { - guint i; - gboolean has_tooltip = FALSE; + GtkLabel *self = GTK_LABEL (widget); + GtkLabelSelectionInfo *info = self->select_info; + int index = -1; - for (i = 0; i < self->select_info->n_links; i++) + if (info && info->links) { - const GtkLabelLink *link = &self->select_info->links[i]; + if (keyboard_tip) + { + if (info->selection_anchor == info->selection_end) + index = info->selection_anchor; + } + else + { + if (!get_layout_index (self, x, y, &index)) + index = -1; + } - if (link->title) + if (index != -1) { - has_tooltip = TRUE; - break; + const int link_index = _gtk_label_get_link_at (self, index); + + if (link_index != -1) + { + const GtkLabelLink *link = &info->links[link_index]; + + if (link->title) + { + gtk_tooltip_set_markup (tooltip, link->title); + } + } } } - gtk_widget_set_has_tooltip (GTK_WIDGET (self), has_tooltip); + return GTK_WIDGET_CLASS (gtk_label_parent_class)->query_tooltip (widget, + x, y, + keyboard_tip, + tooltip); } -/* Reads @text and extracts the accel key, if any. - * @new_text will be set to the given text with the first _ removed. - * - * Returned will be the one underline attribute used for the mnemonic - * */ -static void -extract_mnemonic_keyval (const char *text, - guint *out_accel_key, - char **out_new_text, - PangoAttribute **out_mnemonic_attribute) +static gboolean +gtk_label_focus (GtkWidget *widget, + GtkDirectionType direction) { - const gsize text_len = strlen (text); - gunichar c; - const char *p; + GtkLabel *self = GTK_LABEL (widget); + GtkLabelSelectionInfo *info = self->select_info; + GtkLabelLink *focus_link; - p = text; - for (;;) + if (!gtk_widget_is_focus (widget)) { - const char *_index; + gtk_widget_grab_focus (widget); + if (info) + { + focus_link = gtk_label_get_focus_link (self, NULL); + if (focus_link && direction == GTK_DIR_TAB_BACKWARD) + { + int i; + for (i = info->n_links - 1; i >= 0; i--) + { + focus_link = &info->links[i]; + if (!range_is_in_ellipsis (self, focus_link->start, focus_link->end)) + { + info->selection_anchor = focus_link->start; + info->selection_end = focus_link->start; + } + } + } - c = g_utf8_get_char (p); + return TRUE; + } - if (c == '\0') - break; + return FALSE; + } - if (c != '_') - { - p = g_utf8_next_char (p); - continue; - } + if (!info) + return FALSE; - _index = p; + if (info->selectable) + { + int index; - p = g_utf8_next_char (p); - c = g_utf8_get_char (p); + if (info->selection_anchor != info->selection_end) + goto out; - if (c != '_' && c != '\0') - { - const gsize byte_index = p - text - 1; /* Of the _ */ + index = info->selection_anchor; - /* c is the accel key */ - if (out_accel_key) - *out_accel_key = gdk_keyval_to_lower (gdk_unicode_to_keyval (c)); - if (out_new_text) + if (direction == GTK_DIR_TAB_FORWARD) + { + guint i; + for (i = 0; i < info->n_links; i++) { - *out_new_text = g_malloc (text_len); - memcpy (*out_new_text, text, byte_index); - memcpy (*out_new_text + byte_index, p, text_len - byte_index); - } + const GtkLabelLink *link = &info->links[i]; - if (out_mnemonic_attribute) - { - PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_LOW); - attr->start_index = _index - text; - attr->end_index = p - text; - *out_mnemonic_attribute = attr; + if (link->start > index) + { + if (!range_is_in_ellipsis (self, link->start, link->end)) + { + gtk_label_select_region_index (self, link->start, link->start); + return TRUE; + } + } } - - return; } + else if (direction == GTK_DIR_TAB_BACKWARD) + { + int i; + for (i = info->n_links - 1; i >= 0; i--) + { + GtkLabelLink *link = &info->links[i]; - p = g_utf8_next_char (p); - } + if (link->end < index) + { + if (!range_is_in_ellipsis (self, link->start, link->end)) + { + gtk_label_select_region_index (self, link->start, link->start); + return TRUE; + } + } + } + } - /* No accel key found */ - if (out_accel_key) - *out_accel_key = GDK_KEY_VoidSymbol; - if (out_new_text) - *out_new_text = NULL; - if (out_mnemonic_attribute) - *out_mnemonic_attribute = NULL; + goto out; + } + else + { + int focus_link_index; + int new_index = -1; + int i; + + if (info->n_links == 0) + goto out; + + focus_link = gtk_label_get_focus_link (self, &focus_link_index); + + if (!focus_link) + goto out; + + switch (direction) + { + case GTK_DIR_TAB_FORWARD: + if (focus_link) + new_index = (focus_link_index + 1) % info->n_links; + else + new_index = 0; + + for (i = new_index; i < info->n_links; i++) + { + const GtkLabelLink *link = &info->links[i]; + if (!range_is_in_ellipsis (self, link->start, link->end)) + break; + } + break; + + case GTK_DIR_TAB_BACKWARD: + if (focus_link) + new_index = focus_link_index == 0 ? info->n_links - 1 : focus_link_index - 1; + else + new_index = info->n_links - 1; + + for (i = new_index; i >= 0; i--) + { + const GtkLabelLink *link = &info->links[i]; + if (!range_is_in_ellipsis (self, link->start, link->end)) + break; + } + break; + + default: + case GTK_DIR_UP: + case GTK_DIR_DOWN: + case GTK_DIR_LEFT: + case GTK_DIR_RIGHT: + goto out; + } + + if (new_index != -1) + { + focus_link = &info->links[new_index]; + info->selection_anchor = focus_link->start; + info->selection_end = focus_link->start; + gtk_widget_queue_draw (widget); + + return TRUE; + } + } + +out: + + return FALSE; } static void -gtk_label_set_markup_internal (GtkLabel *self, - const char *str, - gboolean with_uline) +emit_activate_link (GtkLabel *self, + GtkLabelLink *link) { - char *text = NULL; - GError *error = NULL; - PangoAttrList *attrs = NULL; - char *str_for_display = NULL; - GtkLabelLink *links = NULL; - guint n_links = 0; - PangoAttribute *mnemonic_attr = NULL; + gboolean handled; - if (!parse_uri_markup (self, str, &str_for_display, &links, &n_links, &error)) - goto error_set; + g_signal_emit (self, signals[ACTIVATE_LINK], 0, link->uri, &handled); - if (links) + /* signal handler might have invalidated the layout */ + if (!self->layout) + return; + + if (handled && !link->visited && + self->select_info && self->select_info->links) { - gtk_label_ensure_select_info (self); - self->select_info->links = g_steal_pointer (&links); - self->select_info->n_links = n_links; - gtk_label_ensure_has_tooltip (self); - gtk_widget_add_css_class (GTK_WIDGET (self), "link"); + link->visited = TRUE; + update_link_state (self); } +} - if (!with_uline) - { -no_uline: - /* Extract the text to display */ - if (!pango_parse_markup (str_for_display, -1, 0, &attrs, &text, NULL, &error)) - goto error_set; - } - else /* Underline AND markup is a little more complicated... */ - { - char *new_text = NULL; - guint accel_keyval; - gboolean auto_mnemonics = TRUE; - gboolean do_mnemonics = self->mnemonics_visible && - (!auto_mnemonics || gtk_widget_is_sensitive (GTK_WIDGET (self))) && - (!self->mnemonic_widget || gtk_widget_is_sensitive (self->mnemonic_widget)); +static void +gtk_label_activate_link_open (GtkWidget *widget, + const char *name, + GVariant *parameter) +{ + GtkLabel *self = GTK_LABEL (widget); + GtkLabelLink *link = self->select_info->context_link; - /* Remove the mnemonic underline */ - extract_mnemonic_keyval (str_for_display, - &accel_keyval, - &new_text, - NULL); - if (!new_text) /* No underline found anyway */ - goto no_uline; + if (link) + emit_activate_link (self, link); +} - self->mnemonic_keyval = accel_keyval; +static void +gtk_label_activate_link_copy (GtkWidget *widget, + const char *name, + GVariant *parameter) +{ + GtkLabel *self = GTK_LABEL (widget); + GtkLabelLink *link = self->select_info->context_link; - /* Extract the text to display */ - if (!pango_parse_markup (new_text, -1, '_', - do_mnemonics ? &attrs : NULL, &text, NULL, &error)) - goto error_set; + if (link) + { + GdkClipboard *clipboard; - if (do_mnemonics) - { - /* text is now the final text, but we need to parse str_for_display once again - * *with* the mnemonic underline so we can remove the markup tags and get the - * proper attribute indices */ - char *text_for_accel; + clipboard = gtk_widget_get_clipboard (widget); + gdk_clipboard_set_text (clipboard, link->uri); + } + else + g_print ("no link ?!\n"); +} - if (!pango_parse_markup (str_for_display, -1, 0, NULL, &text_for_accel, NULL, &error)) - { - g_free (new_text); - goto error_set; - } +static void +gtk_label_activate_clipboard_copy (GtkWidget *widget, + const char *name, + GVariant *parameter) +{ + g_signal_emit_by_name (widget, "copy-clipboard"); +} - extract_mnemonic_keyval (text_for_accel, - NULL, - NULL, - &mnemonic_attr); - g_free (text_for_accel); - } +static void +gtk_label_select_all (GtkLabel *self) +{ + gtk_label_select_region_index (self, 0, strlen (self->text)); +} - g_free (new_text); - } +static void +gtk_label_activate_selection_select_all (GtkWidget *widget, + const char *name, + GVariant *parameter) +{ + gtk_label_select_all (GTK_LABEL (widget)); +} - g_free (str_for_display); +static void +gtk_label_nop (GtkWidget *widget, + const char *name, + GVariant *parameter) +{ +} - if (text) - gtk_label_set_text_internal (self, text); +static gboolean +gtk_label_mnemonic_activate (GtkWidget *widget, + gboolean group_cycling) +{ + GtkLabel *self = GTK_LABEL (widget); + GtkWidget *parent; + if (self->mnemonic_widget) + return gtk_widget_mnemonic_activate (self->mnemonic_widget, group_cycling); - g_clear_pointer (&self->markup_attrs, pango_attr_list_unref); - self->markup_attrs = attrs; + /* Try to find the widget to activate by traversing the + * widget's ancestry. + */ + parent = gtk_widget_get_parent (widget); - if (mnemonic_attr) - pango_attr_list_insert_before (self->markup_attrs, mnemonic_attr); + if (GTK_IS_NOTEBOOK (parent)) + return FALSE; - return; + while (parent) + { + if (gtk_widget_get_can_focus (parent) || + (!group_cycling && gtk_widget_can_activate (parent)) || + GTK_IS_NOTEBOOK (gtk_widget_get_parent (parent))) + return gtk_widget_mnemonic_activate (parent, group_cycling); + parent = gtk_widget_get_parent (parent); + } -error_set: - g_warning ("Failed to set text '%s' from markup due to error parsing markup: %s", - str, error->message); - g_error_free (error); + /* barf if there was nothing to activate */ + g_warning ("Couldn't find a target for a mnemonic activation."); + gtk_widget_error_bell (widget); + return FALSE; } -/** - * gtk_label_set_markup: - * @self: a #GtkLabel - * @str: a markup string (see [Pango markup format][PangoMarkupFormat]) - * - * Parses @str which is marked up with the - * [Pango text markup language][PangoMarkupFormat], setting the - * label’s text and attribute list based on the parse results. - * - * If the @str is external data, you may need to escape it with - * g_markup_escape_text() or g_markup_printf_escaped(): - * - * |[ - * GtkWidget *self = gtk_label_new (NULL); - * const char *str = "..."; - * const char *format = "\%s"; - * char *markup; - * - * markup = g_markup_printf_escaped (format, str); - * gtk_label_set_markup (GTK_LABEL (self), markup); - * g_free (markup); - * ]| - * - * This function will set the #GtkLabel:use-markup property to %TRUE as - * a side effect. - * - * If you set the label contents using the #GtkLabel:label property you - * should also ensure that you set the #GtkLabel:use-markup property - * accordingly. - * - * See also: gtk_label_set_text() - **/ -void -gtk_label_set_markup (GtkLabel *self, - const char *str) +static void +gtk_label_popup_menu (GtkWidget *widget, + const char *action_name, + GVariant *parameters) { - gboolean changed; + GtkLabel *self = GTK_LABEL (widget); + + gtk_label_do_popup (self, -1, -1); +} + +static void +gtk_label_root (GtkWidget *widget) +{ + GtkLabel *self = GTK_LABEL (widget); + + GTK_WIDGET_CLASS (gtk_label_parent_class)->root (widget); + + gtk_label_setup_mnemonic (self); + + /* The PangoContext is replaced when the display changes, so clear the layouts */ + gtk_label_clear_layout (GTK_LABEL (widget)); +} + +static void +gtk_label_unroot (GtkWidget *widget) +{ + GtkLabel *self = GTK_LABEL (widget); + + gtk_label_setup_mnemonic (self); + + GTK_WIDGET_CLASS (gtk_label_parent_class)->unroot (widget); +} + +static gboolean +gtk_label_activate_link (GtkLabel *self, + const char *uri) +{ + GtkWidget *widget = GTK_WIDGET (self); + GtkWidget *toplevel = GTK_WIDGET (gtk_widget_get_root (widget)); + + if (!GTK_IS_WINDOW (toplevel)) + return FALSE; + + gtk_show_uri (GTK_WINDOW (toplevel), uri, GDK_CURRENT_TIME); + + return TRUE; +} + +static void +gtk_label_activate_current_link (GtkLabel *self) +{ + GtkLabelLink *link; + GtkWidget *widget = GTK_WIDGET (self); + + link = gtk_label_get_focus_link (self, NULL); + + if (link) + emit_activate_link (self, link); + else + gtk_widget_activate_default (widget); +} + +static void +gtk_label_copy_clipboard (GtkLabel *self) +{ + if (self->text && self->select_info) + { + int start, end; + int len; + GdkClipboard *clipboard; + + start = MIN (self->select_info->selection_anchor, + self->select_info->selection_end); + end = MAX (self->select_info->selection_anchor, + self->select_info->selection_end); + + len = strlen (self->text); + + if (end > len) + end = len; + + if (start > len) + start = len; + + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self)); + + if (start != end) + { + char *str = g_strndup (self->text + start, end - start); + gdk_clipboard_set_text (clipboard, str); + g_free (str); + } + else + { + GtkLabelLink *link; + + link = gtk_label_get_focus_link (self, NULL); + if (link) + gdk_clipboard_set_text (clipboard, link->uri); + } + } +} + +static void +gtk_label_class_init (GtkLabelClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + gobject_class->set_property = gtk_label_set_property; + gobject_class->get_property = gtk_label_get_property; + gobject_class->finalize = gtk_label_finalize; + gobject_class->dispose = gtk_label_dispose; + + widget_class->size_allocate = gtk_label_size_allocate; + widget_class->state_flags_changed = gtk_label_state_flags_changed; + widget_class->css_changed = gtk_label_css_changed; + widget_class->query_tooltip = gtk_label_query_tooltip; + widget_class->snapshot = gtk_label_snapshot; + widget_class->unrealize = gtk_label_unrealize; + widget_class->root = gtk_label_root; + widget_class->unroot = gtk_label_unroot; + widget_class->mnemonic_activate = gtk_label_mnemonic_activate; + widget_class->grab_focus = gtk_label_grab_focus; + widget_class->focus = gtk_label_focus; + widget_class->get_request_mode = gtk_label_get_request_mode; + widget_class->measure = gtk_label_measure; + + class->move_cursor = gtk_label_move_cursor; + class->copy_clipboard = gtk_label_copy_clipboard; + class->activate_link = gtk_label_activate_link; + + /** + * GtkLabel::move-cursor: + * @entry: the object which received the signal + * @step: the granularity of the move, as a #GtkMovementStep + * @count: the number of @step units to move + * @extend_selection: %TRUE if the move should extend the selection + * + * The ::move-cursor signal is a + * [keybinding signal][GtkSignalAction] + * which gets emitted when the user initiates a cursor movement. + * If the cursor is not visible in @entry, this signal causes + * the viewport to be moved instead. + * + * Applications should not connect to it, but may emit it with + * g_signal_emit_by_name() if they need to control the cursor + * programmatically. + * + * The default bindings for this signal come in two variants, + * the variant with the Shift modifier extends the selection, + * the variant without the Shift modifier does not. + * There are too many key combinations to list them all here. + * - Arrow keys move by individual characters/lines + * - Ctrl-arrow key combinations move by words/paragraphs + * - Home/End keys move to the ends of the buffer + */ + signals[MOVE_CURSOR] = + g_signal_new (I_("move-cursor"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkLabelClass, move_cursor), + NULL, NULL, + _gtk_marshal_VOID__ENUM_INT_BOOLEAN, + G_TYPE_NONE, 3, + GTK_TYPE_MOVEMENT_STEP, + G_TYPE_INT, + G_TYPE_BOOLEAN); + + /** + * GtkLabel::copy-clipboard: + * @self: the object which received the signal + * + * The ::copy-clipboard signal is a + * [keybinding signal][GtkSignalAction] + * which gets emitted to copy the selection to the clipboard. + * + * The default binding for this signal is Ctrl-c. + */ + signals[COPY_CLIPBOARD] = + g_signal_new (I_("copy-clipboard"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkLabelClass, copy_clipboard), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + /** + * GtkLabel::activate-current-link: + * @self: The label on which the signal was emitted + * + * A [keybinding signal][GtkSignalAction] + * which gets emitted when the user activates a link in the label. + * + * Applications may also emit the signal with g_signal_emit_by_name() + * if they need to control activation of URIs programmatically. + * + * The default bindings for this signal are all forms of the Enter key. + */ + signals[ACTIVATE_CURRENT_LINK] = + g_signal_new_class_handler (I_("activate-current-link"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gtk_label_activate_current_link), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + /** + * GtkLabel::activate-link: + * @self: The label on which the signal was emitted + * @uri: the URI that is activated + * + * The signal which gets emitted to activate a URI. + * Applications may connect to it to override the default behaviour, + * which is to call gtk_show_uri(). + * + * Returns: %TRUE if the link has been activated + */ + signals[ACTIVATE_LINK] = + g_signal_new (I_("activate-link"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkLabelClass, activate_link), + _gtk_boolean_handled_accumulator, NULL, + _gtk_marshal_BOOLEAN__STRING, + G_TYPE_BOOLEAN, 1, G_TYPE_STRING); + + /** + * GtkLabel:label: + * + * The contents of the label. + * + * If the string contains [Pango XML markup][PangoMarkupFormat], you will + * have to set the #GtkLabel:use-markup property to %TRUE in order for the + * label to display the markup attributes. See also gtk_label_set_markup() + * for a convenience function that sets both this property and the + * #GtkLabel:use-markup property at the same time. + * + * If the string contains underlines acting as mnemonics, you will have to + * set the #GtkLabel:use-underline property to %TRUE in order for the label + * to display them. + */ + label_props[PROP_LABEL] = + g_param_spec_string ("label", + P_("Label"), + P_("The text of the label"), + "", + GTK_PARAM_READWRITE); + + label_props[PROP_ATTRIBUTES] = + g_param_spec_boxed ("attributes", + P_("Attributes"), + P_("A list of style attributes to apply to the text of the label"), + PANGO_TYPE_ATTR_LIST, + GTK_PARAM_READWRITE); + + label_props[PROP_USE_MARKUP] = + g_param_spec_boolean ("use-markup", + P_("Use markup"), + P_("The text of the label includes XML markup. See pango_parse_markup()"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + label_props[PROP_USE_UNDERLINE] = + g_param_spec_boolean ("use-underline", + P_("Use underline"), + P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + label_props[PROP_JUSTIFY] = + g_param_spec_enum ("justify", + P_("Justification"), + P_("The alignment of the lines in the text of the label relative to each other. This does NOT affect the alignment of the label within its allocation. See GtkLabel:xalign for that"), + GTK_TYPE_JUSTIFICATION, + GTK_JUSTIFY_LEFT, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkLabel:xalign: + * + * The xalign property determines the horizontal alignment of the label text + * inside the labels size allocation. Compare this to #GtkWidget:halign, + * which determines how the labels size allocation is positioned in the + * space available for the label. + */ + label_props[PROP_XALIGN] = + g_param_spec_float ("xalign", + P_("X align"), + P_("The horizontal alignment, from 0 (left) to 1 (right). Reversed for RTL layouts."), + 0.0, 1.0, + 0.5, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkLabel:yalign: + * + * The yalign property determines the vertical alignment of the label text + * inside the labels size allocation. Compare this to #GtkWidget:valign, + * which determines how the labels size allocation is positioned in the + * space available for the label. + */ + label_props[PROP_YALIGN] = + g_param_spec_float ("yalign", + P_("Y align"), + P_("The vertical alignment, from 0 (top) to 1 (bottom)"), + 0.0, 1.0, + 0.5, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + label_props[PROP_WRAP] = + g_param_spec_boolean ("wrap", + P_("Line wrap"), + P_("If set, wrap lines if the text becomes too wide"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkLabel:wrap-mode: + * + * If line wrapping is on (see the #GtkLabel:wrap property) this controls + * how the line wrapping is done. The default is %PANGO_WRAP_WORD, which + * means wrap on word boundaries. + */ + label_props[PROP_WRAP_MODE] = + g_param_spec_enum ("wrap-mode", + P_("Line wrap mode"), + P_("If wrap is set, controls how linewrapping is done"), + PANGO_TYPE_WRAP_MODE, + PANGO_WRAP_WORD, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + label_props[PROP_SELECTABLE] = + g_param_spec_boolean ("selectable", + P_("Selectable"), + P_("Whether the label text can be selected with the mouse"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + label_props[PROP_MNEMONIC_KEYVAL] = + g_param_spec_uint ("mnemonic-keyval", + P_("Mnemonic key"), + P_("The mnemonic accelerator key for this label"), + 0, G_MAXUINT, + GDK_KEY_VoidSymbol, + GTK_PARAM_READABLE); + + label_props[PROP_MNEMONIC_WIDGET] = + g_param_spec_object ("mnemonic-widget", + P_("Mnemonic widget"), + P_("The widget to be activated when the label’s mnemonic key is pressed"), + GTK_TYPE_WIDGET, + GTK_PARAM_READWRITE); + + /** + * GtkLabel:ellipsize: + * + * The preferred place to ellipsize the string, if the label does + * not have enough room to display the entire string, specified as a + * #PangoEllipsizeMode. + * + * Note that setting this property to a value other than + * %PANGO_ELLIPSIZE_NONE has the side-effect that the label requests + * only enough space to display the ellipsis "...". In particular, this + * means that ellipsizing labels do not work well in notebook tabs, unless + * the #GtkNotebook tab-expand child property is set to %TRUE. Other ways + * to set a label's width are gtk_widget_set_size_request() and + * gtk_label_set_width_chars(). + */ + label_props[PROP_ELLIPSIZE] = + g_param_spec_enum ("ellipsize", + P_("Ellipsize"), + P_("The preferred place to ellipsize the string, if the label does not have enough room to display the entire string"), + PANGO_TYPE_ELLIPSIZE_MODE, + PANGO_ELLIPSIZE_NONE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - g_return_if_fail (GTK_IS_LABEL (self)); + /** + * GtkLabel:width-chars: + * + * The desired width of the label, in characters. If this property is set to + * -1, the width will be calculated automatically. + * + * See the section on [text layout][label-text-layout] + * for details of how #GtkLabel:width-chars and #GtkLabel:max-width-chars + * determine the width of ellipsized and wrapped labels. + **/ + label_props[PROP_WIDTH_CHARS] = + g_param_spec_int ("width-chars", + P_("Width In Characters"), + P_("The desired width of the label, in characters"), + -1, G_MAXINT, + -1, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - g_object_freeze_notify (G_OBJECT (self)); + /** + * GtkLabel:single-line-mode: + * + * Whether the label is in single line mode. In single line mode, + * the height of the label does not depend on the actual text, it + * is always set to ascent + descent of the font. This can be an + * advantage in situations where resizing the label because of text + * changes would be distracting, e.g. in a statusbar. + **/ + label_props[PROP_SINGLE_LINE_MODE] = + g_param_spec_boolean ("single-line-mode", + P_("Single Line Mode"), + P_("Whether the label is in single line mode"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - changed = gtk_label_set_label_internal (self, str); - changed = gtk_label_set_use_markup_internal (self, TRUE) || changed; - changed = gtk_label_set_use_underline_internal (self, FALSE) || changed; + /** + * GtkLabel:max-width-chars: + * + * The desired maximum width of the label, in characters. If this property + * is set to -1, the width will be calculated automatically. + * + * See the section on [text layout][label-text-layout] + * for details of how #GtkLabel:width-chars and #GtkLabel:max-width-chars + * determine the width of ellipsized and wrapped labels. + **/ + label_props[PROP_MAX_WIDTH_CHARS] = + g_param_spec_int ("max-width-chars", + P_("Maximum Width In Characters"), + P_("The desired maximum width of the label, in characters"), + -1, G_MAXINT, + -1, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - if (changed) - gtk_label_recalculate (self); + /** + * GtkLabel:lines: + * + * The number of lines to which an ellipsized, wrapping label + * should be limited. This property has no effect if the + * label is not wrapping or ellipsized. Set this property to + * -1 if you don't want to limit the number of lines. + */ + label_props[PROP_LINES] = + g_param_spec_int ("lines", + P_("Number of lines"), + P_("The desired number of lines, when ellipsizing a wrapping label"), + -1, G_MAXINT, + -1, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - g_object_thaw_notify (G_OBJECT (self)); -} + /** + * GtkLabel:extra-menu: + * + * A menu model whose contents will be appended to + * the context menu. + */ + label_props[PROP_EXTRA_MENU] = + g_param_spec_object ("extra-menu", + P_("Extra menu"), + P_("Menu model to append to the context menu"), + G_TYPE_MENU_MODEL, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); -/** - * gtk_label_set_markup_with_mnemonic: - * @self: a #GtkLabel - * @str: a markup string (see - * [Pango markup format][PangoMarkupFormat]) - * - * Parses @str which is marked up with the - * [Pango text markup language][PangoMarkupFormat], - * setting the label’s text and attribute list based on the parse results. - * If characters in @str are preceded by an underscore, they are underlined - * indicating that they represent a keyboard accelerator called a mnemonic. - * - * The mnemonic key can be used to activate another widget, chosen - * automatically, or explicitly using gtk_label_set_mnemonic_widget(). - */ -void -gtk_label_set_markup_with_mnemonic (GtkLabel *self, - const char *str) -{ - gboolean changed; + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, label_props); - g_return_if_fail (GTK_IS_LABEL (self)); + /** + * GtkLabel|menu.popup: + * + * Opens the context menu. + */ + gtk_widget_class_install_action (widget_class, "menu.popup", NULL, gtk_label_popup_menu); - g_object_freeze_notify (G_OBJECT (self)); + /* + * Key bindings + */ - changed = gtk_label_set_label_internal (self, str); - changed = gtk_label_set_use_markup_internal (self, TRUE) || changed; - changed = gtk_label_set_use_underline_internal (self, TRUE) || changed; + gtk_widget_class_add_binding_action (widget_class, + GDK_KEY_F10, GDK_SHIFT_MASK, + "menu.popup", + NULL); + gtk_widget_class_add_binding_action (widget_class, + GDK_KEY_Menu, 0, + "menu.popup", + NULL); - if (changed) - gtk_label_recalculate (self); + /* Moving the insertion point */ + add_move_binding (widget_class, GDK_KEY_Right, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, 1); - g_object_thaw_notify (G_OBJECT (self)); -} + add_move_binding (widget_class, GDK_KEY_Left, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, -1); -/** - * gtk_label_get_text: - * @self: a #GtkLabel - * - * Fetches the text from a label widget, as displayed on the - * screen. This does not include any embedded underlines - * indicating mnemonics or Pango markup. (See gtk_label_get_label()) - * - * Returns: the text in the label widget. This is the internal - * string used by the label, and must not be modified. - **/ -const char * -gtk_label_get_text (GtkLabel *self) -{ - g_return_val_if_fail (GTK_IS_LABEL (self), NULL); + add_move_binding (widget_class, GDK_KEY_KP_Right, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, 1); - return self->text; -} + add_move_binding (widget_class, GDK_KEY_KP_Left, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, -1); -/** - * gtk_label_set_justify: - * @self: a #GtkLabel - * @jtype: a #GtkJustification - * - * Sets the alignment of the lines in the text of the label relative to - * each other. %GTK_JUSTIFY_LEFT is the default value when the widget is - * first created with gtk_label_new(). If you instead want to set the - * alignment of the label as a whole, use gtk_widget_set_halign() instead. - * gtk_label_set_justify() has no effect on labels containing only a - * single line. - */ -void -gtk_label_set_justify (GtkLabel *self, - GtkJustification jtype) -{ - g_return_if_fail (GTK_IS_LABEL (self)); - g_return_if_fail (jtype >= GTK_JUSTIFY_LEFT && jtype <= GTK_JUSTIFY_FILL); + add_move_binding (widget_class, GDK_KEY_f, GDK_CONTROL_MASK, + GTK_MOVEMENT_LOGICAL_POSITIONS, 1); - if ((GtkJustification) self->jtype != jtype) - { - self->jtype = jtype; + add_move_binding (widget_class, GDK_KEY_b, GDK_CONTROL_MASK, + GTK_MOVEMENT_LOGICAL_POSITIONS, -1); - /* No real need to be this drastic, but easier than duplicating the code */ - gtk_label_clear_layout (self); - - g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_JUSTIFY]); - gtk_widget_queue_resize (GTK_WIDGET (self)); - } -} + add_move_binding (widget_class, GDK_KEY_Right, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, 1); -/** - * gtk_label_get_justify: - * @self: a #GtkLabel - * - * Returns the justification of the label. See gtk_label_set_justify(). - * - * Returns: #GtkJustification - **/ -GtkJustification -gtk_label_get_justify (GtkLabel *self) -{ - g_return_val_if_fail (GTK_IS_LABEL (self), 0); + add_move_binding (widget_class, GDK_KEY_Left, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, -1); - return self->jtype; -} + add_move_binding (widget_class, GDK_KEY_KP_Right, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, 1); -/** - * gtk_label_set_ellipsize: - * @self: a #GtkLabel - * @mode: a #PangoEllipsizeMode - * - * Sets the mode used to ellipsize (add an ellipsis: "...") to the text - * if there is not enough space to render the entire string. - **/ -void -gtk_label_set_ellipsize (GtkLabel *self, - PangoEllipsizeMode mode) -{ - g_return_if_fail (GTK_IS_LABEL (self)); - g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE && mode <= PANGO_ELLIPSIZE_END); + add_move_binding (widget_class, GDK_KEY_KP_Left, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, -1); - if ((PangoEllipsizeMode) self->ellipsize != mode) - { - self->ellipsize = mode; + /* select all */ + gtk_widget_class_add_binding (widget_class, + GDK_KEY_a, GDK_CONTROL_MASK, + (GtkShortcutFunc) gtk_label_select_all, + NULL); + gtk_widget_class_add_binding (widget_class, + GDK_KEY_slash, GDK_CONTROL_MASK, + (GtkShortcutFunc) gtk_label_select_all, + NULL); - /* No real need to be this drastic, but easier than duplicating the code */ - gtk_label_clear_layout (self); + /* unselect all */ + gtk_widget_class_add_binding_signal (widget_class, + GDK_KEY_a, GDK_SHIFT_MASK | GDK_CONTROL_MASK, + "move-cursor", + "(iib)", GTK_MOVEMENT_PARAGRAPH_ENDS, 0, FALSE); - g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_ELLIPSIZE]); - gtk_widget_queue_resize (GTK_WIDGET (self)); - } -} + gtk_widget_class_add_binding_signal (widget_class, + GDK_KEY_backslash, GDK_CONTROL_MASK, + "move-cursor", + "(iib)", GTK_MOVEMENT_PARAGRAPH_ENDS, 0, FALSE); + + add_move_binding (widget_class, GDK_KEY_f, GDK_ALT_MASK, + GTK_MOVEMENT_WORDS, 1); -/** - * gtk_label_get_ellipsize: - * @self: a #GtkLabel - * - * Returns the ellipsizing position of the label. See gtk_label_set_ellipsize(). - * - * Returns: #PangoEllipsizeMode - **/ -PangoEllipsizeMode -gtk_label_get_ellipsize (GtkLabel *self) -{ - g_return_val_if_fail (GTK_IS_LABEL (self), PANGO_ELLIPSIZE_NONE); + add_move_binding (widget_class, GDK_KEY_b, GDK_ALT_MASK, + GTK_MOVEMENT_WORDS, -1); - return self->ellipsize; -} + add_move_binding (widget_class, GDK_KEY_Home, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); -/** - * gtk_label_set_width_chars: - * @self: a #GtkLabel - * @n_chars: the new desired width, in characters. - * - * Sets the desired width in characters of @label to @n_chars. - **/ -void -gtk_label_set_width_chars (GtkLabel *self, - int n_chars) -{ - g_return_if_fail (GTK_IS_LABEL (self)); + add_move_binding (widget_class, GDK_KEY_End, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); - if (self->width_chars != n_chars) - { - self->width_chars = n_chars; - g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_WIDTH_CHARS]); - gtk_widget_queue_resize (GTK_WIDGET (self)); - } -} + add_move_binding (widget_class, GDK_KEY_KP_Home, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); -/** - * gtk_label_get_width_chars: - * @self: a #GtkLabel - * - * Retrieves the desired width of @label, in characters. See - * gtk_label_set_width_chars(). - * - * Returns: the width of the label in characters. - **/ -int -gtk_label_get_width_chars (GtkLabel *self) -{ - g_return_val_if_fail (GTK_IS_LABEL (self), -1); + add_move_binding (widget_class, GDK_KEY_KP_End, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); - return self->width_chars; -} + add_move_binding (widget_class, GDK_KEY_Home, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, -1); -/** - * gtk_label_set_max_width_chars: - * @self: a #GtkLabel - * @n_chars: the new desired maximum width, in characters. - * - * Sets the desired maximum width in characters of @label to @n_chars. - **/ -void -gtk_label_set_max_width_chars (GtkLabel *self, - int n_chars) -{ - g_return_if_fail (GTK_IS_LABEL (self)); + add_move_binding (widget_class, GDK_KEY_End, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, 1); - if (self->max_width_chars != n_chars) - { - self->max_width_chars = n_chars; + add_move_binding (widget_class, GDK_KEY_KP_Home, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, -1); - g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_MAX_WIDTH_CHARS]); - gtk_widget_queue_resize (GTK_WIDGET (self)); - } -} + add_move_binding (widget_class, GDK_KEY_KP_End, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, 1); -/** - * gtk_label_get_max_width_chars: - * @self: a #GtkLabel - * - * Retrieves the desired maximum width of @label, in characters. See - * gtk_label_set_width_chars(). - * - * Returns: the maximum width of the label in characters. - **/ -int -gtk_label_get_max_width_chars (GtkLabel *self) -{ - g_return_val_if_fail (GTK_IS_LABEL (self), -1); + /* copy */ + gtk_widget_class_add_binding_signal (widget_class, + GDK_KEY_c, GDK_CONTROL_MASK, + "copy-clipboard", + NULL); - return self->max_width_chars; -} + gtk_widget_class_add_binding_signal (widget_class, + GDK_KEY_Return, 0, + "activate-current-link", + NULL); + gtk_widget_class_add_binding_signal (widget_class, + GDK_KEY_ISO_Enter, 0, + "activate-current-link", + NULL); + gtk_widget_class_add_binding_signal (widget_class, + GDK_KEY_KP_Enter, 0, + "activate-current-link", + NULL); -/** - * gtk_label_set_wrap: - * @self: a #GtkLabel - * @wrap: the setting - * - * Toggles line wrapping within the #GtkLabel widget. %TRUE makes it break - * lines if text exceeds the widget’s size. %FALSE lets the text get cut off - * by the edge of the widget if it exceeds the widget size. - * - * Note that setting line wrapping to %TRUE does not make the label - * wrap at its parent container’s width, because GTK widgets - * conceptually can’t make their requisition depend on the parent - * container’s size. For a label that wraps at a specific position, - * set the label’s width using gtk_widget_set_size_request(). - **/ -void -gtk_label_set_wrap (GtkLabel *self, - gboolean wrap) -{ - g_return_if_fail (GTK_IS_LABEL (self)); + gtk_widget_class_set_css_name (widget_class, I_("label")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LABEL); - wrap = wrap != FALSE; + quark_mnemonics_visible_connected = g_quark_from_static_string ("gtk-label-mnemonics-visible-connected"); - if (self->wrap != wrap) - { - self->wrap = wrap; + /** + * GtkLabel|clipboard.cut: + * + * Doesn't do anything, since text in labels can't be deleted. + */ + gtk_widget_class_install_action (widget_class, "clipboard.cut", NULL, + gtk_label_nop); - gtk_label_clear_layout (self); - gtk_widget_queue_resize (GTK_WIDGET (self)); - g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_WRAP]); - } -} + /** + * GtkLabel|clipboard.copy: + * + * Copies the text to the clipboard. + */ + gtk_widget_class_install_action (widget_class, "clipboard.copy", NULL, + gtk_label_activate_clipboard_copy); -/** - * gtk_label_get_wrap: - * @self: a #GtkLabel - * - * Returns whether lines in the label are automatically wrapped. - * See gtk_label_set_wrap(). - * - * Returns: %TRUE if the lines of the label are automatically wrapped. - */ -gboolean -gtk_label_get_wrap (GtkLabel *self) -{ - g_return_val_if_fail (GTK_IS_LABEL (self), FALSE); + /** + * GtkLabel|clipboard.paste: + * + * Doesn't do anything, since text in labels can't be edited. + */ + gtk_widget_class_install_action (widget_class, "clipboard.paste", NULL, + gtk_label_nop); - return self->wrap; -} + /** + * GtkLabel|selection.delete: + * + * Doesn't do anything, since text in labels can't be deleted. + */ + gtk_widget_class_install_action (widget_class, "selection.delete", NULL, + gtk_label_nop); -/** - * gtk_label_set_wrap_mode: - * @self: a #GtkLabel - * @wrap_mode: the line wrapping mode - * - * If line wrapping is on (see gtk_label_set_wrap()) this controls how - * the line wrapping is done. The default is %PANGO_WRAP_WORD which means - * wrap on word boundaries. - **/ -void -gtk_label_set_wrap_mode (GtkLabel *self, - PangoWrapMode wrap_mode) -{ - g_return_if_fail (GTK_IS_LABEL (self)); + /** + * GtkLabel|selection.select-all: + * + * Selects all of the text, if the label allows selection. + */ + gtk_widget_class_install_action (widget_class, "selection.select-all", NULL, + gtk_label_activate_selection_select_all); - if (self->wrap_mode != wrap_mode) - { - self->wrap_mode = wrap_mode; - g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_WRAP_MODE]); + /** + * GtkLabel|link.open: + * + * Opens the link, when activated on a link inside the label. + */ + gtk_widget_class_install_action (widget_class, "link.open", NULL, + gtk_label_activate_link_open); - gtk_widget_queue_resize (GTK_WIDGET (self)); - } + /** + * GtkLabel|link.copy: + * + * Copies the link to the clipboard, when activated on a link + * inside the label. + */ + gtk_widget_class_install_action (widget_class, "link.copy", NULL, + gtk_label_activate_link_copy); } /** - * gtk_label_get_wrap_mode: - * @self: a #GtkLabel + * gtk_label_new: + * @str: (nullable): The text of the label * - * Returns line wrap mode used by the label. See gtk_label_set_wrap_mode(). + * Creates a new label with the given text inside it. You can + * pass %NULL to get an empty label widget. * - * Returns: %TRUE if the lines of the label are automatically wrapped. - */ -PangoWrapMode -gtk_label_get_wrap_mode (GtkLabel *self) + * Returns: the new #GtkLabel + **/ +GtkWidget* +gtk_label_new (const char *str) { - g_return_val_if_fail (GTK_IS_LABEL (self), FALSE); - - return self->wrap_mode; -} + GtkLabel *self; -static void -gtk_label_dispose (GObject *object) -{ - GtkLabel *self = GTK_LABEL (object); + self = g_object_new (GTK_TYPE_LABEL, NULL); - gtk_label_set_mnemonic_widget (self, NULL); + if (str && *str) + gtk_label_set_text (self, str); - G_OBJECT_CLASS (gtk_label_parent_class)->dispose (object); + return GTK_WIDGET (self); } -static void -gtk_label_finalize (GObject *object) +/** + * gtk_label_new_with_mnemonic: + * @str: (nullable): The text of the label, with an underscore in front of the + * mnemonic character + * + * Creates a new #GtkLabel, containing the text in @str. + * + * If characters in @str are preceded by an underscore, they are + * underlined. If you need a literal underscore character in a label, use + * '__' (two underscores). The first underlined character represents a + * keyboard accelerator called a mnemonic. The mnemonic key can be used + * to activate another widget, chosen automatically, or explicitly using + * gtk_label_set_mnemonic_widget(). + * + * If gtk_label_set_mnemonic_widget() is not called, then the first + * activatable ancestor of the #GtkLabel will be chosen as the mnemonic + * widget. For instance, if the label is inside a button or menu item, + * the button or menu item will automatically become the mnemonic widget + * and be activated by the mnemonic. + * + * Returns: the new #GtkLabel + **/ +GtkWidget* +gtk_label_new_with_mnemonic (const char *str) { - GtkLabel *self = GTK_LABEL (object); - - g_free (self->label); - g_free (self->text); - - g_clear_object (&self->layout); - g_clear_pointer (&self->attrs, pango_attr_list_unref); - g_clear_pointer (&self->markup_attrs, pango_attr_list_unref); - - if (self->select_info) - g_object_unref (self->select_info->provider); + GtkLabel *self; - gtk_label_clear_links (self); - g_free (self->select_info); + self = g_object_new (GTK_TYPE_LABEL, NULL); - g_clear_pointer (&self->popup_menu, gtk_widget_unparent); - g_clear_object (&self->extra_menu); + if (str && *str) + gtk_label_set_text_with_mnemonic (self, str); - G_OBJECT_CLASS (gtk_label_parent_class)->finalize (object); + return GTK_WIDGET (self); } static void -gtk_label_clear_layout (GtkLabel *self) +label_mnemonics_visible_changed (GtkWidget *widget, + GParamSpec *pspec, + gpointer data) { - g_clear_object (&self->layout); + gboolean visible; + + g_object_get (widget, "mnemonics-visible", &visible, NULL); + _gtk_label_mnemonics_visible_apply_recursively (widget, visible); } -/** - * gtk_label_get_measuring_layout: - * @self: the label - * @existing_layout: %NULL or an existing layout already in use. - * @width: the width to measure with in pango units, or -1 for infinite - * - * Gets a layout that can be used for measuring sizes. The returned - * layout will be identical to the label’s layout except for the - * layout’s width, which will be set to @width. Do not modify the returned - * layout. - * - * Returns: a new reference to a pango layout - **/ -static PangoLayout * -gtk_label_get_measuring_layout (GtkLabel *self, - PangoLayout *existing_layout, - int width) +static void +gtk_label_setup_mnemonic (GtkLabel *self) { - PangoLayout *copy; + GtkWidget *widget = GTK_WIDGET (self); + GtkShortcut *shortcut; + GtkNative *native; + gboolean connected; + gboolean mnemonics_visible; - if (existing_layout != NULL) + if (self->mnemonic_keyval == GDK_KEY_VoidSymbol) { - if (existing_layout != self->layout) + if (self->mnemonic_controller) { - pango_layout_set_width (existing_layout, width); - return existing_layout; + gtk_widget_remove_controller (widget, self->mnemonic_controller); + self->mnemonic_controller = NULL; } + return; + } - g_object_unref (existing_layout); + if (self->mnemonic_controller == NULL) + { + self->mnemonic_controller = gtk_shortcut_controller_new (); + gtk_event_controller_set_propagation_phase (self->mnemonic_controller, GTK_PHASE_CAPTURE); + gtk_shortcut_controller_set_scope (GTK_SHORTCUT_CONTROLLER (self->mnemonic_controller), GTK_SHORTCUT_SCOPE_MANAGED); + shortcut = gtk_shortcut_new (gtk_mnemonic_trigger_new (self->mnemonic_keyval), + g_object_ref (gtk_mnemonic_action_get ())); + gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (self->mnemonic_controller), shortcut); + gtk_widget_add_controller (GTK_WIDGET (self), self->mnemonic_controller); + } + else + { + shortcut = g_list_model_get_item (G_LIST_MODEL (self->mnemonic_controller), 0); + gtk_shortcut_set_trigger (shortcut, gtk_mnemonic_trigger_new (self->mnemonic_keyval)); + g_object_unref (shortcut); } - gtk_label_ensure_layout (self); + /* Connect to notify::mnemonics-visible of the root */ + native = gtk_widget_get_native (GTK_WIDGET (self)); + if (!GTK_IS_WINDOW (native) && !GTK_IS_POPOVER (native)) + return; - if (pango_layout_get_width (self->layout) == width) + /* always set up this widgets initial value */ + g_object_get (native, "mnemonics-visible", &mnemonics_visible, NULL); + self->mnemonics_visible = mnemonics_visible; + + connected = GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (native), + quark_mnemonics_visible_connected)); + + if (!connected) { - g_object_ref (self->layout); - return self->layout; + g_signal_connect (native, + "notify::mnemonics-visible", + G_CALLBACK (label_mnemonics_visible_changed), + self); + g_object_set_qdata (G_OBJECT (native), + quark_mnemonics_visible_connected, + GINT_TO_POINTER (1)); } +} - /* We can use the label's own layout if we're not allocated a size yet, - * because we don't need it to be properly setup at that point. - * This way we can make use of caching upon the label's creation. - */ - if (gtk_widget_get_width (GTK_WIDGET (self)) <= 1) +void +_gtk_label_mnemonics_visible_apply_recursively (GtkWidget *widget, + gboolean visible) +{ + if (GTK_IS_LABEL (widget)) { - g_object_ref (self->layout); - pango_layout_set_width (self->layout, width); - return self->layout; - } + GtkLabel *self = GTK_LABEL (widget); - /* oftentimes we want to measure a width that is far wider than the current width, - * even though the layout would not change if we made it wider. In that case, we - * can just return the current layout, because for measuring purposes, it will be - * identical. - */ - if (!pango_layout_is_wrapped (self->layout) && - !pango_layout_is_ellipsized (self->layout)) + if (self->mnemonics_visible != visible) + { + self->mnemonics_visible = visible; + gtk_label_recalculate (self); + } + } + else { - PangoRectangle rect; + GtkWidget *child; - if (width == -1) - return g_object_ref (self->layout); + for (child = gtk_widget_get_first_child (widget); + child; + child = gtk_widget_get_next_sibling (child)) + { + if (GTK_IS_NATIVE (child)) + continue; - pango_layout_get_extents (self->layout, NULL, &rect); - if (rect.width <= width) - return g_object_ref (self->layout); + _gtk_label_mnemonics_visible_apply_recursively (child, visible); + } } +} +static void +label_mnemonic_widget_weak_notify (gpointer data, + GObject *where_the_object_was) +{ + GtkLabel *self = data; - copy = pango_layout_copy (self->layout); - pango_layout_set_width (copy, width); - return copy; + self->mnemonic_widget = NULL; + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_MNEMONIC_WIDGET]); } -static void -gtk_label_update_layout_attributes (GtkLabel *self, - PangoAttrList *style_attrs) +/** + * gtk_label_set_mnemonic_widget: + * @self: a #GtkLabel + * @widget: (nullable): the target #GtkWidget, or %NULL to unset + * + * If the label has been set so that it has a mnemonic key (using + * i.e. gtk_label_set_markup_with_mnemonic(), + * gtk_label_set_text_with_mnemonic(), gtk_label_new_with_mnemonic() + * or the “use_underline” property) the label can be associated with a + * widget that is the target of the mnemonic. When the label is inside + * a widget (like a #GtkButton or a #GtkNotebook tab) it is + * automatically associated with the correct widget, but sometimes + * (i.e. when the target is a #GtkEntry next to the label) you need to + * set it explicitly using this function. + * + * The target widget will be accelerated by emitting the + * GtkWidget::mnemonic-activate signal on it. The default handler for + * this signal will activate the widget if there are no mnemonic collisions + * and toggle focus between the colliding widgets otherwise. + **/ +void +gtk_label_set_mnemonic_widget (GtkLabel *self, + GtkWidget *widget) { - GtkWidget *widget = GTK_WIDGET (self); - GtkCssStyle *style; - PangoAttrList *attrs; + g_return_if_fail (GTK_IS_LABEL (self)); - if (self->layout == NULL) + if (widget) + g_return_if_fail (GTK_IS_WIDGET (widget)); + + if (self->mnemonic_widget) { - pango_attr_list_unref (style_attrs); - return; + gtk_widget_remove_mnemonic_label (self->mnemonic_widget, GTK_WIDGET (self)); + g_object_weak_unref (G_OBJECT (self->mnemonic_widget), + label_mnemonic_widget_weak_notify, + self); } - - if (self->select_info && self->select_info->links) + self->mnemonic_widget = widget; + if (self->mnemonic_widget) { - guint i; - - attrs = pango_attr_list_new (); - - for (i = 0; i < self->select_info->n_links; i++) - { - const GtkLabelLink *link = &self->select_info->links[i]; - const GdkRGBA *link_color; - PangoAttrList *link_attrs; - PangoAttribute *attr; - - style = gtk_css_node_get_style (link->cssnode); - link_attrs = gtk_css_style_get_pango_attributes (style); - if (link_attrs) - { - GSList *attributes = pango_attr_list_get_attributes (link_attrs); - GSList *l; - for (l = attributes; l; l = l->next) - { - attr = l->data; + g_object_weak_ref (G_OBJECT (self->mnemonic_widget), + label_mnemonic_widget_weak_notify, + self); + gtk_widget_add_mnemonic_label (self->mnemonic_widget, GTK_WIDGET (self)); + } - attr->start_index = link->start; - attr->end_index = link->end; - pango_attr_list_insert (attrs, attr); - } - g_slist_free (attributes); - } + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_MNEMONIC_WIDGET]); +} - link_color = gtk_css_color_value_get_rgba (style->core->color); - attr = pango_attr_foreground_new (link_color->red * 65535, - link_color->green * 65535, - link_color->blue * 65535); - attr->start_index = link->start; - attr->end_index = link->end; - pango_attr_list_insert (attrs, attr); +/** + * gtk_label_get_mnemonic_widget: + * @self: a #GtkLabel + * + * Retrieves the target of the mnemonic (keyboard shortcut) of this + * label. See gtk_label_set_mnemonic_widget(). + * + * Returns: (nullable) (transfer none): the target of the label’s mnemonic, + * or %NULL if none has been set and the default algorithm will be used. + **/ +GtkWidget * +gtk_label_get_mnemonic_widget (GtkLabel *self) +{ + g_return_val_if_fail (GTK_IS_LABEL (self), NULL); - pango_attr_list_unref (link_attrs); - } - } - else - attrs = NULL; + return self->mnemonic_widget; +} - style = gtk_css_node_get_style (gtk_widget_get_css_node (widget)); - if (!style_attrs) - style_attrs = gtk_css_style_get_pango_attributes (style); +/** + * gtk_label_get_mnemonic_keyval: + * @self: a #GtkLabel + * + * If the label has been set so that it has a mnemonic key this function + * returns the keyval used for the mnemonic accelerator. If there is no + * mnemonic set up it returns #GDK_KEY_VoidSymbol. + * + * Returns: GDK keyval usable for accelerators, or #GDK_KEY_VoidSymbol + **/ +guint +gtk_label_get_mnemonic_keyval (GtkLabel *self) +{ + g_return_val_if_fail (GTK_IS_LABEL (self), GDK_KEY_VoidSymbol); - if (style_attrs) + return self->mnemonic_keyval; +} + +static void +gtk_label_set_text_internal (GtkLabel *self, + char *str) +{ + if (g_strcmp0 (self->text, str) == 0) { - attrs = _gtk_pango_attr_list_merge (attrs, style_attrs); - pango_attr_list_unref (style_attrs); + g_free (str); + return; } - attrs = _gtk_pango_attr_list_merge (attrs, self->markup_attrs); - attrs = _gtk_pango_attr_list_merge (attrs, self->attrs); + g_free (self->text); + self->text = str; - pango_layout_set_attributes (self->layout, attrs); + gtk_accessible_update_property (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_PROPERTY_LABEL, self->text, + -1); - pango_attr_list_unref (attrs); + gtk_label_select_region_index (self, 0, 0); } -static void -gtk_label_ensure_layout (GtkLabel *self) +static gboolean +gtk_label_set_label_internal (GtkLabel *self, + const char *str) { - PangoAlignment align; - gboolean rtl; + if (g_strcmp0 (str, self->label) == 0) + return FALSE; - if (self->layout) - return; + g_free (self->label); + self->label = g_strdup (str ? str : ""); - align = PANGO_ALIGN_LEFT; /* Quiet gcc */ - rtl = _gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL; - self->layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), self->text); + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_LABEL]); - gtk_label_update_layout_attributes (self, NULL); + return TRUE; +} - switch (self->jtype) +static gboolean +gtk_label_set_use_markup_internal (GtkLabel *self, + gboolean val) +{ + if (self->use_markup != val) { - case GTK_JUSTIFY_LEFT: - align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT; - break; - case GTK_JUSTIFY_RIGHT: - align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT; - break; - case GTK_JUSTIFY_CENTER: - align = PANGO_ALIGN_CENTER; - break; - case GTK_JUSTIFY_FILL: - align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT; - pango_layout_set_justify (self->layout, TRUE); - break; - default: - g_assert_not_reached(); - } + self->use_markup = val; - pango_layout_set_alignment (self->layout, align); - pango_layout_set_ellipsize (self->layout, self->ellipsize); - pango_layout_set_wrap (self->layout, self->wrap_mode); - pango_layout_set_single_paragraph_mode (self->layout, self->single_line_mode); - if (self->lines > 0) - pango_layout_set_height (self->layout, - self->lines); + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_USE_MARKUP]); - if (self->ellipsize || self->wrap) - pango_layout_set_width (self->layout, gtk_widget_get_width (GTK_WIDGET (self)) * PANGO_SCALE); + return TRUE; + } + + return FALSE; } -static GtkSizeRequestMode -gtk_label_get_request_mode (GtkWidget *widget) +static gboolean +gtk_label_set_use_underline_internal (GtkLabel *self, + gboolean val) { - GtkLabel *self = GTK_LABEL (widget); + if (self->use_underline != val) + { + self->use_underline = val; - if (self->wrap) - return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_USE_UNDERLINE]); - return GTK_SIZE_REQUEST_CONSTANT_SIZE; -} + return TRUE; + } + return FALSE; +} +/* Calculates text, attrs and mnemonic_keyval from + * label, use_underline and use_markup + */ static void -get_height_for_width (GtkLabel *self, - int width, - int *minimum_height, - int *natural_height, - int *minimum_baseline, - int *natural_baseline) +gtk_label_recalculate (GtkLabel *self) { - PangoLayout *layout; - int text_height, baseline; + guint keyval = self->mnemonic_keyval; - layout = gtk_label_get_measuring_layout (self, NULL, width * PANGO_SCALE); + gtk_label_clear_links (self); + gtk_label_clear_layout (self); + gtk_label_clear_select_info (self); - pango_layout_get_pixel_size (layout, NULL, &text_height); + if (self->use_markup || self->use_underline) + gtk_label_set_markup_internal (self, self->label, self->use_underline); + else + { + g_clear_pointer (&self->markup_attrs, pango_attr_list_unref); - *minimum_height = text_height; - *natural_height = text_height; + gtk_label_set_text_internal (self, g_strdup (self->label)); + } - baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - *minimum_baseline = baseline; - *natural_baseline = baseline; + if (!self->use_underline) + self->mnemonic_keyval = GDK_KEY_VoidSymbol; - g_object_unref (layout); + if (keyval != self->mnemonic_keyval) + { + gtk_label_setup_mnemonic (self); + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_MNEMONIC_KEYVAL]); + } + + gtk_widget_queue_resize (GTK_WIDGET (self)); } -static int -get_char_pixels (GtkWidget *self, - PangoLayout *layout) +/** + * gtk_label_set_text: + * @self: a #GtkLabel + * @str: The text you want to set + * + * Sets the text within the #GtkLabel widget. It overwrites any text that + * was there before. + * + * This function will clear any previously set mnemonic accelerators, and + * set the #GtkLabel:use-underline property to %FALSE as a side effect. + * + * This function will set the #GtkLabel:use-markup property to %FALSE + * as a side effect. + * + * See also: gtk_label_set_markup() + **/ +void +gtk_label_set_text (GtkLabel *self, + const char *str) { - PangoContext *context; - PangoFontMetrics *metrics; - int char_width, digit_width; - - context = pango_layout_get_context (layout); - metrics = pango_context_get_metrics (context, - pango_context_get_font_description (context), - pango_context_get_language (context)); - char_width = pango_font_metrics_get_approximate_char_width (metrics); - digit_width = pango_font_metrics_get_approximate_digit_width (metrics); - pango_font_metrics_unref (metrics); + gboolean changed; - return MAX (char_width, digit_width);; -} + g_return_if_fail (GTK_IS_LABEL (self)); -static void -gtk_label_get_preferred_layout_size (GtkLabel *self, - PangoRectangle *smallest, - PangoRectangle *widest, - int *smallest_baseline, - int *widest_baseline) -{ - PangoLayout *layout; - int char_pixels; + g_object_freeze_notify (G_OBJECT (self)); - /* "width-chars" Hard-coded minimum width: - * - minimum size should be MAX (width-chars, strlen ("...")); - * - natural size should be MAX (width-chars, strlen (self->text)); - * - * "max-width-chars" User specified maximum size requisition - * - minimum size should be MAX (width-chars, 0) - * - natural size should be MIN (max-width-chars, strlen (self->text)) - * - * For ellipsizing labels; if max-width-chars is specified: either it is used as - * a minimum size or the label text as a minimum size (natural size still overflows). - * - * For wrapping labels; A reasonable minimum size is useful to naturally layout - * interfaces automatically. In this case if no "width-chars" is specified, the minimum - * width will default to the wrap guess that gtk_label_ensure_layout() does. - */ + changed = gtk_label_set_label_internal (self, str); + changed = gtk_label_set_use_markup_internal (self, FALSE) || changed; + changed = gtk_label_set_use_underline_internal (self, FALSE) || changed; - /* Start off with the pixel extents of an as-wide-as-possible layout */ - layout = gtk_label_get_measuring_layout (self, NULL, -1); + if (changed) + gtk_label_recalculate (self); - if (self->width_chars > -1 || self->max_width_chars > -1) - char_pixels = get_char_pixels (GTK_WIDGET (self), layout); - else - char_pixels = 0; + g_object_thaw_notify (G_OBJECT (self)); +} - pango_layout_get_extents (layout, NULL, widest); - widest->width = MAX (widest->width, char_pixels * self->width_chars); - widest->x = widest->y = 0; - *widest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; +/** + * gtk_label_set_attributes: + * @self: a #GtkLabel + * @attrs: (nullable): a #PangoAttrList, or %NULL + * + * Sets a #PangoAttrList; the attributes in the list are applied to the + * label text. + * + * The attributes set with this function will be applied + * and merged with any other attributes previously effected by way + * of the #GtkLabel:use-underline or #GtkLabel:use-markup properties. + * While it is not recommended to mix markup strings with manually set + * attributes, if you must; know that the attributes will be applied + * to the label after the markup string is parsed. + **/ +void +gtk_label_set_attributes (GtkLabel *self, + PangoAttrList *attrs) +{ + g_return_if_fail (GTK_IS_LABEL (self)); - if (self->ellipsize || self->wrap) - { - /* a layout with width 0 will be as small as humanly possible */ - layout = gtk_label_get_measuring_layout (self, - layout, - self->width_chars > -1 ? char_pixels * self->width_chars - : 0); + if (!attrs && !self->attrs) + return; - pango_layout_get_extents (layout, NULL, smallest); - smallest->width = MAX (smallest->width, char_pixels * self->width_chars); - smallest->x = smallest->y = 0; + if (attrs) + pango_attr_list_ref (attrs); - *smallest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; + if (self->attrs) + pango_attr_list_unref (self->attrs); + self->attrs = attrs; - if (self->max_width_chars > -1 && widest->width > char_pixels * self->max_width_chars) - { - layout = gtk_label_get_measuring_layout (self, - layout, - MAX (smallest->width, char_pixels * self->max_width_chars)); - pango_layout_get_extents (layout, NULL, widest); - widest->width = MAX (widest->width, char_pixels * self->width_chars); - widest->x = widest->y = 0; + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_ATTRIBUTES]); - *widest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - } + gtk_label_clear_layout (self); + gtk_widget_queue_resize (GTK_WIDGET (self)); +} - if (widest->width < smallest->width) - { - *smallest = *widest; - *smallest_baseline = *widest_baseline; - } - } - else - { - *smallest = *widest; - *smallest_baseline = *widest_baseline; - } +/** + * gtk_label_get_attributes: + * @self: a #GtkLabel + * + * Gets the attribute list that was set on the label using + * gtk_label_set_attributes(), if any. This function does + * not reflect attributes that come from the labels markup + * (see gtk_label_set_markup()). If you want to get the + * effective attributes for the label, use + * pango_layout_get_attribute (gtk_label_get_layout (self)). + * + * Returns: (nullable) (transfer none): the attribute list, or %NULL + * if none was set. + **/ +PangoAttrList * +gtk_label_get_attributes (GtkLabel *self) +{ + g_return_val_if_fail (GTK_IS_LABEL (self), NULL); - g_object_unref (layout); + return self->attrs; } -static void -gtk_label_get_preferred_size (GtkWidget *widget, - GtkOrientation orientation, - int *minimum_size, - int *natural_size, - int *minimum_baseline, - int *natural_baseline) +/** + * gtk_label_set_label: + * @self: a #GtkLabel + * @str: the new text to set for the label + * + * Sets the text of the label. The label is interpreted as + * including embedded underlines and/or Pango markup depending + * on the values of the #GtkLabel:use-underline and + * #GtkLabel:use-markup properties. + **/ +void +gtk_label_set_label (GtkLabel *self, + const char *str) { - GtkLabel *self = GTK_LABEL (widget); - PangoRectangle widest_rect; - PangoRectangle smallest_rect; - int smallest_baseline; - int widest_baseline; - - gtk_label_get_preferred_layout_size (self, - &smallest_rect, &widest_rect, - &smallest_baseline, &widest_baseline); + g_return_if_fail (GTK_IS_LABEL (self)); - widest_rect.width = PANGO_PIXELS_CEIL (widest_rect.width); - widest_rect.height = PANGO_PIXELS_CEIL (widest_rect.height); + g_object_freeze_notify (G_OBJECT (self)); - smallest_rect.width = PANGO_PIXELS_CEIL (smallest_rect.width); - smallest_rect.height = PANGO_PIXELS_CEIL (smallest_rect.height); + if (gtk_label_set_label_internal (self, str)) + gtk_label_recalculate (self); - if (orientation == GTK_ORIENTATION_HORIZONTAL) - { - /* Normal desired width */ - *minimum_size = smallest_rect.width; - *natural_size = widest_rect.width; + g_object_thaw_notify (G_OBJECT (self)); +} - if (minimum_baseline) - *minimum_baseline = -1; +/** + * gtk_label_get_label: + * @self: a #GtkLabel + * + * Fetches the text from a label widget including any embedded + * underlines indicating mnemonics and Pango markup. (See + * gtk_label_get_text()). + * + * Returns: the text of the label widget. This string is + * owned by the widget and must not be modified or freed. + **/ +const char * +gtk_label_get_label (GtkLabel *self) +{ + g_return_val_if_fail (GTK_IS_LABEL (self), NULL); - if (natural_baseline) - *natural_baseline = -1; - } - else /* GTK_ORIENTATION_VERTICAL */ - { - if (smallest_rect.height < widest_rect.height) - { - *minimum_size = smallest_rect.height; - *natural_size = widest_rect.height; - if (minimum_baseline) - *minimum_baseline = smallest_baseline; - if (natural_baseline) - *natural_baseline = widest_baseline; - } - else - { - *minimum_size = widest_rect.height; - *natural_size = smallest_rect.height; - if (minimum_baseline) - *minimum_baseline = widest_baseline; - if (natural_baseline) - *natural_baseline = smallest_baseline; - } - } + return self->label; } +typedef struct +{ + GtkLabel *label; + GArray *links; + GString *new_str; + gsize text_len; +} UriParserData; + static void -gtk_label_measure (GtkWidget *widget, - GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline) +start_element_handler (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + gpointer user_data, + GError **error) { - GtkLabel *self = GTK_LABEL (widget); + UriParserData *pdata = user_data; + GtkLabel *self = pdata->label; - if (orientation == GTK_ORIENTATION_VERTICAL && for_size != -1 && self->wrap) + if (strcmp (element_name, "a") == 0) { - gtk_label_clear_layout (self); + GtkLabelLink link; + const char *uri = NULL; + const char *title = NULL; + const char *class = NULL; + gboolean visited = FALSE; + int line_number; + int char_number; + int i; + GtkCssNode *widget_node; + GtkStateFlags state; - get_height_for_width (self, for_size, minimum, natural, minimum_baseline, natural_baseline); - } - else - gtk_label_get_preferred_size (widget, orientation, minimum, natural, minimum_baseline, natural_baseline); -} + g_markup_parse_context_get_position (context, &line_number, &char_number); -static void -get_layout_location (GtkLabel *self, - int *xp, - int *yp) -{ - GtkWidget *widget = GTK_WIDGET (self); - int layout_width, layout_height, x, y; - float xalign, yalign; - PangoRectangle logical; - int baseline, layout_baseline, baseline_offset; - int widget_width, widget_height; + for (i = 0; attribute_names[i] != NULL; i++) + { + const char *attr = attribute_names[i]; - xalign = self->xalign; - yalign = self->yalign; + if (strcmp (attr, "href") == 0) + uri = attribute_values[i]; + else if (strcmp (attr, "title") == 0) + title = attribute_values[i]; + else if (strcmp (attr, "class") == 0) + class = attribute_values[i]; + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, + "Attribute '%s' is not allowed on the tag " + "on line %d char %d", + attr, line_number, char_number); + return; + } + } - if (_gtk_widget_get_direction (widget) != GTK_TEXT_DIR_LTR) - xalign = 1.0 - xalign; + if (uri == NULL) + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Attribute 'href' was missing on the tag " + "on line %d char %d", + line_number, char_number); + return; + } - pango_layout_get_pixel_extents (self->layout, NULL, &logical); + visited = FALSE; + if (self->select_info) + { + for (i = 0; i < self->select_info->n_links; i++) + { + const GtkLabelLink *l = &self->select_info->links[i]; + + if (strcmp (uri, l->uri) == 0) + { + visited = l->visited; + break; + } + } + } - layout_width = logical.width; - layout_height = logical.height; + if (!pdata->links) + pdata->links = g_array_new (FALSE, TRUE, sizeof (GtkLabelLink)); - widget_width = gtk_widget_get_width (widget); - widget_height = gtk_widget_get_height (widget); + link.uri = g_strdup (uri); + link.title = g_strdup (title); - baseline = gtk_widget_get_allocated_baseline (widget); + widget_node = gtk_widget_get_css_node (GTK_WIDGET (pdata->label)); + link.cssnode = gtk_css_node_new (); + gtk_css_node_set_name (link.cssnode, g_quark_from_static_string ("link")); + gtk_css_node_set_parent (link.cssnode, widget_node); + if (class) + gtk_css_node_add_class (link.cssnode, g_quark_from_string (class)); - x = floor ((xalign * (widget_width - layout_width)) - logical.x); + state = gtk_css_node_get_state (widget_node); + if (visited) + state |= GTK_STATE_FLAG_VISITED; + else + state |= GTK_STATE_FLAG_LINK; + gtk_css_node_set_state (link.cssnode, state); + g_object_unref (link.cssnode); - baseline_offset = 0; - if (baseline != -1) - { - layout_baseline = pango_layout_get_baseline (self->layout) / PANGO_SCALE; - baseline_offset = baseline - layout_baseline; - yalign = 0.0; /* Can't support yalign while baseline aligning */ + link.visited = visited; + link.start = pdata->text_len; + g_array_append_val (pdata->links, link); } + else + { + int i; - y = floor ((widget_height - layout_height) * yalign) + baseline_offset; + g_string_append_c (pdata->new_str, '<'); + g_string_append (pdata->new_str, element_name); - if (xp) - *xp = x; + for (i = 0; attribute_names[i] != NULL; i++) + { + const char *attr = attribute_names[i]; + const char *value = attribute_values[i]; + char *newvalue; - if (yp) - *yp = y; -} + newvalue = g_markup_escape_text (value, -1); -static void -gtk_label_size_allocate (GtkWidget *widget, - int width, - int height, - int baseline) -{ - GtkLabel *self = GTK_LABEL (widget); + g_string_append_c (pdata->new_str, ' '); + g_string_append (pdata->new_str, attr); + g_string_append (pdata->new_str, "=\""); + g_string_append (pdata->new_str, newvalue); + g_string_append_c (pdata->new_str, '\"'); - if (self->layout) - { - if (self->ellipsize || self->wrap) - pango_layout_set_width (self->layout, width * PANGO_SCALE); - else - pango_layout_set_width (self->layout, -1); + g_free (newvalue); + } + g_string_append_c (pdata->new_str, '>'); } - - if (self->popup_menu) - gtk_popover_present (GTK_POPOVER (self->popup_menu)); } static void -gtk_label_update_cursor (GtkLabel *self) +end_element_handler (GMarkupParseContext *context, + const char *element_name, + gpointer user_data, + GError **error) { - GtkWidget *widget = GTK_WIDGET (self); - - if (!self->select_info) - return; + UriParserData *pdata = user_data; - if (gtk_widget_is_sensitive (widget)) + if (!strcmp (element_name, "a")) { - if (self->select_info->active_link) - gtk_widget_set_cursor_from_name (widget, "pointer"); - else if (self->select_info->selectable) - gtk_widget_set_cursor_from_name (widget, "text"); - else - gtk_widget_set_cursor (widget, NULL); + GtkLabelLink *link = &g_array_index (pdata->links, GtkLabelLink, pdata->links->len - 1); + link->end = pdata->text_len; } else - gtk_widget_set_cursor (widget, NULL); + { + g_string_append (pdata->new_str, "new_str, element_name); + g_string_append_c (pdata->new_str, '>'); + } } static void -update_link_state (GtkLabel *self) +text_handler (GMarkupParseContext *context, + const char *text, + gsize text_len, + gpointer user_data, + GError **error) { - GtkStateFlags state; - guint i; + UriParserData *pdata = user_data; + char *newtext; - if (!self->select_info) - return; + newtext = g_markup_escape_text (text, text_len); + g_string_append (pdata->new_str, newtext); + pdata->text_len += text_len; + g_free (newtext); +} - for (i = 0; i < self->select_info->n_links; i++) - { - const GtkLabelLink *link = &self->select_info->links[i]; +static const GMarkupParser markup_parser = +{ + start_element_handler, + end_element_handler, + text_handler, + NULL, + NULL +}; - state = gtk_widget_get_state_flags (GTK_WIDGET (self)); - if (link->visited) - state |= GTK_STATE_FLAG_VISITED; - else - state |= GTK_STATE_FLAG_LINK; - if (link == self->select_info->active_link) - { - if (self->select_info->link_clicked) - state |= GTK_STATE_FLAG_ACTIVE; - else - state |= GTK_STATE_FLAG_PRELIGHT; - } - gtk_css_node_set_state (link->cssnode, state); - } +static gboolean +xml_isspace (char c) +{ + return (c == ' ' || c == '\t' || c == '\n' || c == '\r'); } -static void -gtk_label_state_flags_changed (GtkWidget *widget, - GtkStateFlags prev_state) +static gboolean +parse_uri_markup (GtkLabel *self, + const char *str, + char **new_str, + GtkLabelLink **links, + guint *out_n_links, + GError **error) { - GtkLabel *self = GTK_LABEL (widget); - - if (self->select_info) - { - if (!gtk_widget_is_sensitive (widget)) - gtk_label_select_region (self, 0, 0); + GMarkupParseContext *context; + const char *p, *end; + gsize length; + UriParserData pdata; - gtk_label_update_cursor (self); - update_link_state (self); - } + length = strlen (str); + p = str; + end = str + length; - if (GTK_WIDGET_CLASS (gtk_label_parent_class)->state_flags_changed) - GTK_WIDGET_CLASS (gtk_label_parent_class)->state_flags_changed (widget, prev_state); -} + pdata.label = self; + pdata.links = NULL; + pdata.new_str = g_string_sized_new (length); + pdata.text_len = 0; -static void -gtk_label_css_changed (GtkWidget *widget, - GtkCssStyleChange *change) -{ - GtkLabel *self = GTK_LABEL (widget); - gboolean attrs_affected; - PangoAttrList *new_attrs = NULL; + while (p != end && xml_isspace (*p)) + p++; - GTK_WIDGET_CLASS (gtk_label_parent_class)->css_changed (widget, change); + context = g_markup_parse_context_new (&markup_parser, 0, &pdata, NULL); - if (gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_TEXT_ATTRS)) + if (end - p >= 8 && strncmp (p, "", 8) == 0) { - new_attrs = gtk_css_style_get_pango_attributes (gtk_css_style_change_get_new_style (change)); - attrs_affected = (self->layout && pango_layout_get_attributes (self->layout)) || - new_attrs; + if (!g_markup_parse_context_parse (context, str, length, error)) + goto failed; } else - attrs_affected = FALSE; - - if (change == NULL || attrs_affected || (self->select_info && self->select_info->links)) { - gtk_label_update_layout_attributes (self, new_attrs); + if (!g_markup_parse_context_parse (context, "", 8, error)) + goto failed; - if (attrs_affected) - gtk_widget_queue_draw (widget); + if (!g_markup_parse_context_parse (context, str, length, error)) + goto failed; + + if (!g_markup_parse_context_parse (context, "", 9, error)) + goto failed; } -} -static PangoDirection -get_cursor_direction (GtkLabel *self) -{ - GSList *l; + if (!g_markup_parse_context_end_parse (context, error)) + goto failed; - g_assert (self->select_info); + g_markup_parse_context_free (context); - gtk_label_ensure_layout (self); + *new_str = g_string_free (pdata.new_str, FALSE); - for (l = pango_layout_get_lines_readonly (self->layout); l; l = l->next) + if (pdata.links) { - PangoLayoutLine *line = l->data; - - /* If self->select_info->selection_end is at the very end of - * the line, we don't know if the cursor is on this line or - * the next without looking ahead at the next line. (End - * of paragraph is different from line break.) But it's - * definitely in this paragraph, which is good enough - * to figure out the resolved direction. - */ - if (line->start_index + line->length >= self->select_info->selection_end) - return line->resolved_dir; + *out_n_links = pdata.links->len; + *links = (GtkLabelLink *)g_array_free (pdata.links, FALSE); + } + else + { + *links = NULL; } - return PANGO_DIRECTION_LTR; -} + return TRUE; -static GtkLabelLink * -gtk_label_get_focus_link (GtkLabel *self, - int *out_index) -{ - GtkLabelSelectionInfo *info = self->select_info; - int link_index; +failed: + g_markup_parse_context_free (context); + g_string_free (pdata.new_str, TRUE); - if (!info || - info->selection_anchor != info->selection_end) - goto nope; + if (pdata.links) + g_array_free (pdata.links, TRUE); + + return FALSE; +} - link_index = _gtk_label_get_link_at (self, info->selection_anchor); +static void +gtk_label_ensure_has_tooltip (GtkLabel *self) +{ + guint i; + gboolean has_tooltip = FALSE; - if (link_index != -1) + for (i = 0; i < self->select_info->n_links; i++) { - if (out_index) - *out_index = link_index; + const GtkLabelLink *link = &self->select_info->links[i]; - return &info->links[link_index]; + if (link->title) + { + has_tooltip = TRUE; + break; + } } -nope: - if (out_index) - *out_index = -1; - return NULL; + gtk_widget_set_has_tooltip (GTK_WIDGET (self), has_tooltip); } -#define GRAPHENE_RECT_FROM_RECT(_r) (GRAPHENE_RECT_INIT ((_r)->x, (_r)->y, (_r)->width, (_r)->height)) - +/* Reads @text and extracts the accel key, if any. + * @new_text will be set to the given text with the first _ removed. + * + * Returned will be the one underline attribute used for the mnemonic + * */ static void -gtk_label_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot) +extract_mnemonic_keyval (const char *text, + guint *out_accel_key, + char **out_new_text, + PangoAttribute **out_mnemonic_attribute) { - GtkLabel *self = GTK_LABEL (widget); - GtkLabelSelectionInfo *info; - GtkStyleContext *context; - int lx, ly; - int width, height; - - if (!self->text || (*self->text == '\0')) - return; + const gsize text_len = strlen (text); + gunichar c; + const char *p; - gtk_label_ensure_layout (self); + p = text; + for (;;) + { + const char *_index; - context = _gtk_widget_get_style_context (widget); - get_layout_location (self, &lx, &ly); + c = g_utf8_get_char (p); - gtk_snapshot_render_layout (snapshot, context, lx, ly, self->layout); + if (c == '\0') + break; - info = self->select_info; - if (!info) - return; + if (c != '_') + { + p = g_utf8_next_char (p); + continue; + } - width = gtk_widget_get_width (widget); - height = gtk_widget_get_height (widget); + _index = p; - if (info->selection_anchor != info->selection_end) - { - int range[2]; - cairo_region_t *range_clip; - cairo_rectangle_int_t clip_rect; - int i; + p = g_utf8_next_char (p); + c = g_utf8_get_char (p); - range[0] = MIN (info->selection_anchor, info->selection_end); - range[1] = MAX (info->selection_anchor, info->selection_end); + if (c != '_' && c != '\0') + { + const gsize byte_index = p - text - 1; /* Of the _ */ - gtk_style_context_save_to_node (context, info->selection_node); + /* c is the accel key */ + if (out_accel_key) + *out_accel_key = gdk_keyval_to_lower (gdk_unicode_to_keyval (c)); + if (out_new_text) + { + *out_new_text = g_malloc (text_len); + memcpy (*out_new_text, text, byte_index); + memcpy (*out_new_text + byte_index, p, text_len - byte_index); + } - range_clip = gdk_pango_layout_get_clip_region (self->layout, lx, ly, range, 1); - for (i = 0; i < cairo_region_num_rectangles (range_clip); i++) - { - cairo_region_get_rectangle (range_clip, i, &clip_rect); + if (out_mnemonic_attribute) + { + PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_LOW); + attr->start_index = _index - text; + attr->end_index = p - text; + *out_mnemonic_attribute = attr; + } - gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_FROM_RECT (&clip_rect)); - gtk_snapshot_render_background (snapshot, context, 0, 0, width, height); - gtk_snapshot_render_layout (snapshot, context, lx, ly, self->layout); - gtk_snapshot_pop (snapshot); + return; } - cairo_region_destroy (range_clip); - - gtk_style_context_restore (context); + p = g_utf8_next_char (p); } - else + + /* No accel key found */ + if (out_accel_key) + *out_accel_key = GDK_KEY_VoidSymbol; + if (out_new_text) + *out_new_text = NULL; + if (out_mnemonic_attribute) + *out_mnemonic_attribute = NULL; +} + +static void +gtk_label_set_markup_internal (GtkLabel *self, + const char *str, + gboolean with_uline) +{ + char *text = NULL; + GError *error = NULL; + PangoAttrList *attrs = NULL; + char *str_for_display = NULL; + GtkLabelLink *links = NULL; + guint n_links = 0; + PangoAttribute *mnemonic_attr = NULL; + + if (!parse_uri_markup (self, str, &str_for_display, &links, &n_links, &error)) + goto error_set; + + if (links) { - GtkLabelLink *focus_link; - GtkLabelLink *active_link; - int range[2]; - cairo_region_t *range_clip; - cairo_rectangle_int_t clip_rect; - int i; - GdkRectangle rect; + gtk_label_ensure_select_info (self); + self->select_info->links = g_steal_pointer (&links); + self->select_info->n_links = n_links; + gtk_label_ensure_has_tooltip (self); + gtk_widget_add_css_class (GTK_WIDGET (self), "link"); + } - if (info->selectable && - gtk_widget_has_focus (widget) && - gtk_widget_is_drawable (widget)) + if (!with_uline) + { +no_uline: + /* Extract the text to display */ + if (!pango_parse_markup (str_for_display, -1, 0, &attrs, &text, NULL, &error)) + goto error_set; + } + else /* Underline AND markup is a little more complicated... */ + { + char *new_text = NULL; + guint accel_keyval; + gboolean auto_mnemonics = TRUE; + gboolean do_mnemonics = self->mnemonics_visible && + (!auto_mnemonics || gtk_widget_is_sensitive (GTK_WIDGET (self))) && + (!self->mnemonic_widget || gtk_widget_is_sensitive (self->mnemonic_widget)); + + /* Remove the mnemonic underline */ + extract_mnemonic_keyval (str_for_display, + &accel_keyval, + &new_text, + NULL); + if (!new_text) /* No underline found anyway */ + goto no_uline; + + self->mnemonic_keyval = accel_keyval; + + /* Extract the text to display */ + if (!pango_parse_markup (new_text, -1, '_', + do_mnemonics ? &attrs : NULL, &text, NULL, &error)) + goto error_set; + + if (do_mnemonics) { - PangoDirection cursor_direction; + /* text is now the final text, but we need to parse str_for_display once again + * *with* the mnemonic underline so we can remove the markup tags and get the + * proper attribute indices */ + char *text_for_accel; - cursor_direction = get_cursor_direction (self); - gtk_snapshot_render_insertion_cursor (snapshot, context, - lx, ly, - self->layout, self->select_info->selection_end, - cursor_direction); + if (!pango_parse_markup (str_for_display, -1, 0, NULL, &text_for_accel, NULL, &error)) + { + g_free (new_text); + goto error_set; + } + + extract_mnemonic_keyval (text_for_accel, + NULL, + NULL, + &mnemonic_attr); + g_free (text_for_accel); } - focus_link = gtk_label_get_focus_link (self, NULL); - active_link = info->active_link; + g_free (new_text); + } - if (active_link) - { - range[0] = active_link->start; - range[1] = active_link->end; + g_free (str_for_display); - gtk_style_context_save_to_node (context, active_link->cssnode); + if (text) + gtk_label_set_text_internal (self, text); - range_clip = gdk_pango_layout_get_clip_region (self->layout, lx, ly, range, 1); - for (i = 0; i < cairo_region_num_rectangles (range_clip); i++) - { - cairo_region_get_rectangle (range_clip, i, &clip_rect); + g_clear_pointer (&self->markup_attrs, pango_attr_list_unref); + self->markup_attrs = attrs; - gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_FROM_RECT (&clip_rect)); - gtk_snapshot_render_background (snapshot, context, 0, 0, width, height); - gtk_snapshot_render_layout (snapshot, context, lx, ly, self->layout); - gtk_snapshot_pop (snapshot); - } + if (mnemonic_attr) + pango_attr_list_insert_before (self->markup_attrs, mnemonic_attr); - cairo_region_destroy (range_clip); + return; - gtk_style_context_restore (context); - } +error_set: + g_warning ("Failed to set text '%s' from markup due to error parsing markup: %s", + str, error->message); + g_error_free (error); - if (focus_link && gtk_widget_has_visible_focus (widget)) - { - range[0] = focus_link->start; - range[1] = focus_link->end; +} + +/** + * gtk_label_set_markup: + * @self: a #GtkLabel + * @str: a markup string (see [Pango markup format][PangoMarkupFormat]) + * + * Parses @str which is marked up with the + * [Pango text markup language][PangoMarkupFormat], setting the + * label’s text and attribute list based on the parse results. + * + * If the @str is external data, you may need to escape it with + * g_markup_escape_text() or g_markup_printf_escaped(): + * + * |[ + * GtkWidget *self = gtk_label_new (NULL); + * const char *str = "..."; + * const char *format = "\%s"; + * char *markup; + * + * markup = g_markup_printf_escaped (format, str); + * gtk_label_set_markup (GTK_LABEL (self), markup); + * g_free (markup); + * ]| + * + * This function will set the #GtkLabel:use-markup property to %TRUE as + * a side effect. + * + * If you set the label contents using the #GtkLabel:label property you + * should also ensure that you set the #GtkLabel:use-markup property + * accordingly. + * + * See also: gtk_label_set_text() + **/ +void +gtk_label_set_markup (GtkLabel *self, + const char *str) +{ + gboolean changed; - gtk_style_context_save_to_node (context, focus_link->cssnode); + g_return_if_fail (GTK_IS_LABEL (self)); - range_clip = gdk_pango_layout_get_clip_region (self->layout, lx, ly, range, 1); - cairo_region_get_extents (range_clip, &rect); + g_object_freeze_notify (G_OBJECT (self)); - gtk_snapshot_render_focus (snapshot, context, rect.x, rect.y, rect.width, rect.height); + changed = gtk_label_set_label_internal (self, str); + changed = gtk_label_set_use_markup_internal (self, TRUE) || changed; + changed = gtk_label_set_use_underline_internal (self, FALSE) || changed; - cairo_region_destroy (range_clip); + if (changed) + gtk_label_recalculate (self); - gtk_style_context_restore (context); - } - } + g_object_thaw_notify (G_OBJECT (self)); } /** - * gtk_label_set_text_with_mnemonic: + * gtk_label_set_markup_with_mnemonic: * @self: a #GtkLabel - * @str: a string + * @str: a markup string (see + * [Pango markup format][PangoMarkupFormat]) * - * Sets the label’s text from the string @str. + * Parses @str which is marked up with the + * [Pango text markup language][PangoMarkupFormat], + * setting the label’s text and attribute list based on the parse results. * If characters in @str are preceded by an underscore, they are underlined * indicating that they represent a keyboard accelerator called a mnemonic. + * * The mnemonic key can be used to activate another widget, chosen * automatically, or explicitly using gtk_label_set_mnemonic_widget(). - **/ + */ void -gtk_label_set_text_with_mnemonic (GtkLabel *self, - const char *str) +gtk_label_set_markup_with_mnemonic (GtkLabel *self, + const char *str) { gboolean changed; g_return_if_fail (GTK_IS_LABEL (self)); - g_return_if_fail (str != NULL); g_object_freeze_notify (G_OBJECT (self)); changed = gtk_label_set_label_internal (self, str); - changed = gtk_label_set_use_markup_internal (self, FALSE) || changed; + changed = gtk_label_set_use_markup_internal (self, TRUE) || changed; changed = gtk_label_set_use_underline_internal (self, TRUE) || changed; if (changed) @@ -3567,344 +3611,433 @@ gtk_label_set_text_with_mnemonic (GtkLabel *self, g_object_thaw_notify (G_OBJECT (self)); } -static void -gtk_label_unrealize (GtkWidget *widget) +/** + * gtk_label_get_text: + * @self: a #GtkLabel + * + * Fetches the text from a label widget, as displayed on the + * screen. This does not include any embedded underlines + * indicating mnemonics or Pango markup. (See gtk_label_get_label()) + * + * Returns: the text in the label widget. This is the internal + * string used by the label, and must not be modified. + **/ +const char * +gtk_label_get_text (GtkLabel *self) { - GtkLabel *self = GTK_LABEL (widget); + g_return_val_if_fail (GTK_IS_LABEL (self), NULL); - if (self->select_info && - self->select_info->provider) + return self->text; +} + +/** + * gtk_label_set_justify: + * @self: a #GtkLabel + * @jtype: a #GtkJustification + * + * Sets the alignment of the lines in the text of the label relative to + * each other. %GTK_JUSTIFY_LEFT is the default value when the widget is + * first created with gtk_label_new(). If you instead want to set the + * alignment of the label as a whole, use gtk_widget_set_halign() instead. + * gtk_label_set_justify() has no effect on labels containing only a + * single line. + */ +void +gtk_label_set_justify (GtkLabel *self, + GtkJustification jtype) +{ + g_return_if_fail (GTK_IS_LABEL (self)); + g_return_if_fail (jtype >= GTK_JUSTIFY_LEFT && jtype <= GTK_JUSTIFY_FILL); + + if ((GtkJustification) self->jtype != jtype) { - GdkClipboard *clipboard = gtk_widget_get_primary_clipboard (widget); + self->jtype = jtype; - if (gdk_clipboard_get_content (clipboard) == self->select_info->provider) - gdk_clipboard_set_content (clipboard, NULL); + /* No real need to be this drastic, but easier than duplicating the code */ + gtk_label_clear_layout (self); + + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_JUSTIFY]); + gtk_widget_queue_resize (GTK_WIDGET (self)); } +} - GTK_WIDGET_CLASS (gtk_label_parent_class)->unrealize (widget); +/** + * gtk_label_get_justify: + * @self: a #GtkLabel + * + * Returns the justification of the label. See gtk_label_set_justify(). + * + * Returns: #GtkJustification + **/ +GtkJustification +gtk_label_get_justify (GtkLabel *self) +{ + g_return_val_if_fail (GTK_IS_LABEL (self), 0); + + return self->jtype; } -static gboolean -get_layout_index (GtkLabel *self, - int x, - int y, - int *index) +/** + * gtk_label_set_ellipsize: + * @self: a #GtkLabel + * @mode: a #PangoEllipsizeMode + * + * Sets the mode used to ellipsize (add an ellipsis: "...") to the text + * if there is not enough space to render the entire string. + **/ +void +gtk_label_set_ellipsize (GtkLabel *self, + PangoEllipsizeMode mode) { - int trailing = 0; - const char *cluster; - const char *cluster_end; - gboolean inside; - int lx, ly; + g_return_if_fail (GTK_IS_LABEL (self)); + g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE && mode <= PANGO_ELLIPSIZE_END); - *index = 0; + if ((PangoEllipsizeMode) self->ellipsize != mode) + { + self->ellipsize = mode; - gtk_label_ensure_layout (self); - get_layout_location (self, &lx, &ly); + /* No real need to be this drastic, but easier than duplicating the code */ + gtk_label_clear_layout (self); - /* Translate x/y to layout position */ - x -= lx; - y -= ly; + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_ELLIPSIZE]); + gtk_widget_queue_resize (GTK_WIDGET (self)); + } +} - x *= PANGO_SCALE; - y *= PANGO_SCALE; +/** + * gtk_label_get_ellipsize: + * @self: a #GtkLabel + * + * Returns the ellipsizing position of the label. See gtk_label_set_ellipsize(). + * + * Returns: #PangoEllipsizeMode + **/ +PangoEllipsizeMode +gtk_label_get_ellipsize (GtkLabel *self) +{ + g_return_val_if_fail (GTK_IS_LABEL (self), PANGO_ELLIPSIZE_NONE); - inside = pango_layout_xy_to_index (self->layout, - x, y, - index, &trailing); + return self->ellipsize; +} - cluster = self->text + *index; - cluster_end = cluster; - while (trailing) +/** + * gtk_label_set_width_chars: + * @self: a #GtkLabel + * @n_chars: the new desired width, in characters. + * + * Sets the desired width in characters of @label to @n_chars. + **/ +void +gtk_label_set_width_chars (GtkLabel *self, + int n_chars) +{ + g_return_if_fail (GTK_IS_LABEL (self)); + + if (self->width_chars != n_chars) { - cluster_end = g_utf8_next_char (cluster_end); - --trailing; + self->width_chars = n_chars; + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_WIDTH_CHARS]); + gtk_widget_queue_resize (GTK_WIDGET (self)); } +} - *index += (cluster_end - cluster); +/** + * gtk_label_get_width_chars: + * @self: a #GtkLabel + * + * Retrieves the desired width of @label, in characters. See + * gtk_label_set_width_chars(). + * + * Returns: the width of the label in characters. + **/ +int +gtk_label_get_width_chars (GtkLabel *self) +{ + g_return_val_if_fail (GTK_IS_LABEL (self), -1); + + return self->width_chars; +} + +/** + * gtk_label_set_max_width_chars: + * @self: a #GtkLabel + * @n_chars: the new desired maximum width, in characters. + * + * Sets the desired maximum width in characters of @label to @n_chars. + **/ +void +gtk_label_set_max_width_chars (GtkLabel *self, + int n_chars) +{ + g_return_if_fail (GTK_IS_LABEL (self)); + + if (self->max_width_chars != n_chars) + { + self->max_width_chars = n_chars; + + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_MAX_WIDTH_CHARS]); + gtk_widget_queue_resize (GTK_WIDGET (self)); + } +} + +/** + * gtk_label_get_max_width_chars: + * @self: a #GtkLabel + * + * Retrieves the desired maximum width of @label, in characters. See + * gtk_label_set_width_chars(). + * + * Returns: the maximum width of the label in characters. + **/ +int +gtk_label_get_max_width_chars (GtkLabel *self) +{ + g_return_val_if_fail (GTK_IS_LABEL (self), -1); - return inside; + return self->max_width_chars; } -static gboolean -range_is_in_ellipsis_full (GtkLabel *self, - int range_start, - int range_end, - int *ellipsis_start, - int *ellipsis_end) +/** + * gtk_label_set_wrap: + * @self: a #GtkLabel + * @wrap: the setting + * + * Toggles line wrapping within the #GtkLabel widget. %TRUE makes it break + * lines if text exceeds the widget’s size. %FALSE lets the text get cut off + * by the edge of the widget if it exceeds the widget size. + * + * Note that setting line wrapping to %TRUE does not make the label + * wrap at its parent container’s width, because GTK widgets + * conceptually can’t make their requisition depend on the parent + * container’s size. For a label that wraps at a specific position, + * set the label’s width using gtk_widget_set_size_request(). + **/ +void +gtk_label_set_wrap (GtkLabel *self, + gboolean wrap) { - PangoLayoutIter *iter; - gboolean in_ellipsis; - - if (!self->ellipsize) - return FALSE; - - gtk_label_ensure_layout (self); - - if (!pango_layout_is_ellipsized (self->layout)) - return FALSE; + g_return_if_fail (GTK_IS_LABEL (self)); - iter = pango_layout_get_iter (self->layout); + wrap = wrap != FALSE; - in_ellipsis = FALSE; + if (self->wrap != wrap) + { + self->wrap = wrap; - do { - PangoLayoutRun *run; + gtk_label_clear_layout (self); + gtk_widget_queue_resize (GTK_WIDGET (self)); + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_WRAP]); + } +} - run = pango_layout_iter_get_run_readonly (iter); - if (run) - { - PangoItem *item; +/** + * gtk_label_get_wrap: + * @self: a #GtkLabel + * + * Returns whether lines in the label are automatically wrapped. + * See gtk_label_set_wrap(). + * + * Returns: %TRUE if the lines of the label are automatically wrapped. + */ +gboolean +gtk_label_get_wrap (GtkLabel *self) +{ + g_return_val_if_fail (GTK_IS_LABEL (self), FALSE); - item = ((PangoGlyphItem*)run)->item; + return self->wrap; +} - if (item->offset <= range_start && range_end <= item->offset + item->length) - { - if (item->analysis.flags & PANGO_ANALYSIS_FLAG_IS_ELLIPSIS) - { - if (ellipsis_start) - *ellipsis_start = item->offset; - if (ellipsis_end) - *ellipsis_end = item->offset + item->length; - in_ellipsis = TRUE; - } - break; - } - else if (item->offset + item->length >= range_end) - break; - } - } while (pango_layout_iter_next_run (iter)); +/** + * gtk_label_set_wrap_mode: + * @self: a #GtkLabel + * @wrap_mode: the line wrapping mode + * + * If line wrapping is on (see gtk_label_set_wrap()) this controls how + * the line wrapping is done. The default is %PANGO_WRAP_WORD which means + * wrap on word boundaries. + **/ +void +gtk_label_set_wrap_mode (GtkLabel *self, + PangoWrapMode wrap_mode) +{ + g_return_if_fail (GTK_IS_LABEL (self)); - pango_layout_iter_free (iter); + if (self->wrap_mode != wrap_mode) + { + self->wrap_mode = wrap_mode; + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_WRAP_MODE]); - return in_ellipsis; + gtk_widget_queue_resize (GTK_WIDGET (self)); + } } -static gboolean -range_is_in_ellipsis (GtkLabel *self, - int range_start, - int range_end) +/** + * gtk_label_get_wrap_mode: + * @self: a #GtkLabel + * + * Returns line wrap mode used by the label. See gtk_label_set_wrap_mode(). + * + * Returns: %TRUE if the lines of the label are automatically wrapped. + */ +PangoWrapMode +gtk_label_get_wrap_mode (GtkLabel *self) { - return range_is_in_ellipsis_full (self, range_start, range_end, NULL, NULL); + g_return_val_if_fail (GTK_IS_LABEL (self), FALSE); + + return self->wrap_mode; } static void -gtk_label_select_word (GtkLabel *self) +gtk_label_clear_layout (GtkLabel *self) { - int min, max; - - int start_index = gtk_label_move_backward_word (self, self->select_info->selection_end); - int end_index = gtk_label_move_forward_word (self, self->select_info->selection_end); - - min = MIN (self->select_info->selection_anchor, - self->select_info->selection_end); - max = MAX (self->select_info->selection_anchor, - self->select_info->selection_end); - - min = MIN (min, start_index); - max = MAX (max, end_index); - - gtk_label_select_region_index (self, min, max); + g_clear_object (&self->layout); } -static gboolean -gtk_label_grab_focus (GtkWidget *widget) +static void +gtk_label_ensure_layout (GtkLabel *self) { - GtkLabel *self = GTK_LABEL (widget); - gboolean select_on_focus; - GtkWidget *prev_focus; + PangoAlignment align; + gboolean rtl; - if (self->select_info == NULL) - return FALSE; + if (self->layout) + return; - prev_focus = gtk_root_get_focus (gtk_widget_get_root (widget)); + align = PANGO_ALIGN_LEFT; /* Quiet gcc */ + rtl = _gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL; + self->layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), self->text); - if (!GTK_WIDGET_CLASS (gtk_label_parent_class)->grab_focus (widget)) - return FALSE; + gtk_label_update_layout_attributes (self, NULL); - if (self->select_info->selectable) + switch (self->jtype) { - g_object_get (gtk_widget_get_settings (widget), - "gtk-label-select-on-focus", - &select_on_focus, - NULL); - - if (select_on_focus && !self->in_click && - !(prev_focus && gtk_widget_is_ancestor (prev_focus, widget))) - gtk_label_select_region (self, 0, -1); + case GTK_JUSTIFY_LEFT: + align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT; + break; + case GTK_JUSTIFY_RIGHT: + align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT; + break; + case GTK_JUSTIFY_CENTER: + align = PANGO_ALIGN_CENTER; + break; + case GTK_JUSTIFY_FILL: + align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT; + pango_layout_set_justify (self->layout, TRUE); + break; + default: + g_assert_not_reached(); } - else - { - if (self->select_info->links && !self->in_click && - !(prev_focus && gtk_widget_is_ancestor (prev_focus, widget))) - { - guint i; - - for (i = 0; i < self->select_info->n_links; i++) - { - const GtkLabelLink *link = &self->select_info->links[i]; - if (!range_is_in_ellipsis (self, link->start, link->end)) - { - self->select_info->selection_anchor = link->start; - self->select_info->selection_end = link->start; - break; - } - } - } - } + pango_layout_set_alignment (self->layout, align); + pango_layout_set_ellipsize (self->layout, self->ellipsize); + pango_layout_set_wrap (self->layout, self->wrap_mode); + pango_layout_set_single_paragraph_mode (self->layout, self->single_line_mode); + if (self->lines > 0) + pango_layout_set_height (self->layout, - self->lines); - return TRUE; + if (self->ellipsize || self->wrap) + pango_layout_set_width (self->layout, gtk_widget_get_width (GTK_WIDGET (self)) * PANGO_SCALE); } -static gboolean -gtk_label_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkLabel *self = GTK_LABEL (widget); - GtkLabelSelectionInfo *info = self->select_info; - GtkLabelLink *focus_link; - - if (!gtk_widget_is_focus (widget)) - { - gtk_widget_grab_focus (widget); - if (info) - { - focus_link = gtk_label_get_focus_link (self, NULL); - if (focus_link && direction == GTK_DIR_TAB_BACKWARD) - { - int i; - for (i = info->n_links - 1; i >= 0; i--) - { - focus_link = &info->links[i]; - if (!range_is_in_ellipsis (self, focus_link->start, focus_link->end)) - { - info->selection_anchor = focus_link->start; - info->selection_end = focus_link->start; - } - } - } +/** + * gtk_label_set_text_with_mnemonic: + * @self: a #GtkLabel + * @str: a string + * + * Sets the label’s text from the string @str. + * If characters in @str are preceded by an underscore, they are underlined + * indicating that they represent a keyboard accelerator called a mnemonic. + * The mnemonic key can be used to activate another widget, chosen + * automatically, or explicitly using gtk_label_set_mnemonic_widget(). + **/ +void +gtk_label_set_text_with_mnemonic (GtkLabel *self, + const char *str) +{ + gboolean changed; - return TRUE; - } + g_return_if_fail (GTK_IS_LABEL (self)); + g_return_if_fail (str != NULL); - return FALSE; - } + g_object_freeze_notify (G_OBJECT (self)); - if (!info) - return FALSE; + changed = gtk_label_set_label_internal (self, str); + changed = gtk_label_set_use_markup_internal (self, FALSE) || changed; + changed = gtk_label_set_use_underline_internal (self, TRUE) || changed; - if (info->selectable) - { - int index; + if (changed) + gtk_label_recalculate (self); - if (info->selection_anchor != info->selection_end) - goto out; + g_object_thaw_notify (G_OBJECT (self)); +} - index = info->selection_anchor; +static int +gtk_label_move_forward_word (GtkLabel *self, + int start) +{ + int new_pos = g_utf8_pointer_to_offset (self->text, self->text + start); + int length; - if (direction == GTK_DIR_TAB_FORWARD) - { - guint i; - for (i = 0; i < info->n_links; i++) - { - const GtkLabelLink *link = &info->links[i]; + length = g_utf8_strlen (self->text, -1); + if (new_pos < length) + { + const PangoLogAttr *log_attrs; + int n_attrs; - if (link->start > index) - { - if (!range_is_in_ellipsis (self, link->start, link->end)) - { - gtk_label_select_region_index (self, link->start, link->start); - return TRUE; - } - } - } - } - else if (direction == GTK_DIR_TAB_BACKWARD) - { - int i; - for (i = info->n_links - 1; i >= 0; i--) - { - GtkLabelLink *link = &info->links[i]; + gtk_label_ensure_layout (self); - if (link->end < index) - { - if (!range_is_in_ellipsis (self, link->start, link->end)) - { - gtk_label_select_region_index (self, link->start, link->start); - return TRUE; - } - } - } - } + log_attrs = pango_layout_get_log_attrs_readonly (self->layout, &n_attrs); - goto out; + /* Find the next word end */ + new_pos++; + while (new_pos < n_attrs && !log_attrs[new_pos].is_word_end) + new_pos++; } - else - { - int focus_link_index; - int new_index = -1; - int i; - if (info->n_links == 0) - goto out; + return g_utf8_offset_to_pointer (self->text, new_pos) - self->text; +} - focus_link = gtk_label_get_focus_link (self, &focus_link_index); +static int +gtk_label_move_backward_word (GtkLabel *self, + int start) +{ + int new_pos = g_utf8_pointer_to_offset (self->text, self->text + start); - if (!focus_link) - goto out; + if (new_pos > 0) + { + const PangoLogAttr *log_attrs; + int n_attrs; - switch (direction) - { - case GTK_DIR_TAB_FORWARD: - if (focus_link) - new_index = (focus_link_index + 1) % info->n_links; - else - new_index = 0; + gtk_label_ensure_layout (self); - for (i = new_index; i < info->n_links; i++) - { - const GtkLabelLink *link = &info->links[i]; - if (!range_is_in_ellipsis (self, link->start, link->end)) - break; - } - break; + log_attrs = pango_layout_get_log_attrs_readonly (self->layout, &n_attrs); - case GTK_DIR_TAB_BACKWARD: - if (focus_link) - new_index = focus_link_index == 0 ? info->n_links - 1 : focus_link_index - 1; - else - new_index = info->n_links - 1; + new_pos -= 1; - for (i = new_index; i >= 0; i--) - { - const GtkLabelLink *link = &info->links[i]; - if (!range_is_in_ellipsis (self, link->start, link->end)) - break; - } - break; + /* Find the previous word beginning */ + while (new_pos > 0 && !log_attrs[new_pos].is_word_start) + new_pos--; + } - default: - case GTK_DIR_UP: - case GTK_DIR_DOWN: - case GTK_DIR_LEFT: - case GTK_DIR_RIGHT: - goto out; - } + return g_utf8_offset_to_pointer (self->text, new_pos) - self->text; +} - if (new_index != -1) - { - focus_link = &info->links[new_index]; - info->selection_anchor = focus_link->start; - info->selection_end = focus_link->start; - gtk_widget_queue_draw (widget); +static void +gtk_label_select_word (GtkLabel *self) +{ + int min, max; - return TRUE; - } - } + int start_index = gtk_label_move_backward_word (self, self->select_info->selection_end); + int end_index = gtk_label_move_forward_word (self, self->select_info->selection_end); -out: + min = MIN (self->select_info->selection_anchor, + self->select_info->selection_end); + max = MAX (self->select_info->selection_anchor, + self->select_info->selection_end); - return FALSE; + min = MIN (min, start_index); + max = MAX (max, end_index); + + gtk_label_select_region_index (self, min, max); } static void @@ -4139,7 +4272,7 @@ gtk_label_drag_gesture_update (GtkGestureDrag *gesture, if (info->in_drag) { if (gtk_drag_check_threshold (widget, info->drag_start_x, info->drag_start_y, x, y)) - { + { GdkDrag *drag; GdkSurface *surface; GdkDevice *device; @@ -4157,9 +4290,8 @@ gtk_label_drag_gesture_update (GtkGestureDrag *gesture, gtk_drag_icon_set_from_paintable (drag, get_selection_paintable (self), 0, 0); g_object_unref (drag); - - info->in_drag = FALSE; - } + info->in_drag = FALSE; + } } else { @@ -4211,6 +4343,34 @@ gtk_label_drag_gesture_update (GtkGestureDrag *gesture, } } +static void +gtk_label_update_actions (GtkLabel *self) +{ + GtkWidget *widget = GTK_WIDGET (self); + gboolean has_selection; + GtkLabelLink *link; + + if (self->select_info) + { + has_selection = self->select_info->selection_anchor != self->select_info->selection_end; + link = self->select_info->active_link; + } + else + { + has_selection = FALSE; + link = gtk_label_get_focus_link (self, NULL); + } + + gtk_widget_action_set_enabled (widget, "clipboard.cut", FALSE); + gtk_widget_action_set_enabled (widget, "clipboard.copy", has_selection); + gtk_widget_action_set_enabled (widget, "clipboard.paste", FALSE); + gtk_widget_action_set_enabled (widget, "selection.select-all", + gtk_label_get_selectable (self)); + gtk_widget_action_set_enabled (widget, "selection.delete", FALSE); + gtk_widget_action_set_enabled (widget, "link.open", !has_selection && link); + gtk_widget_action_set_enabled (widget, "link.copy", !has_selection && link); +} + static void gtk_label_update_active_link (GtkWidget *widget, double x, @@ -4530,9 +4690,9 @@ gtk_label_set_selectable (GtkLabel *self, /** * gtk_label_get_selectable: * @self: a #GtkLabel - * + * * Gets the value set by gtk_label_set_selectable(). - * + * * Returns: %TRUE if the user can copy text from the label **/ gboolean @@ -4678,10 +4838,10 @@ gtk_label_select_region (GtkLabel *self, { if (start_offset < 0) start_offset = g_utf8_strlen (self->text, -1); - + if (end_offset < 0) end_offset = g_utf8_strlen (self->text, -1); - + gtk_label_select_region_index (self, g_utf8_offset_to_pointer (self->text, start_offset) - self->text, g_utf8_offset_to_pointer (self->text, end_offset) - self->text); @@ -4693,10 +4853,10 @@ gtk_label_select_region (GtkLabel *self, * @self: a #GtkLabel * @start: (out): return location for start of selection, as a character offset * @end: (out): return location for end of selection, as a character offset - * + * * Gets the selected range of characters in the label, returning %TRUE * if there’s a selection. - * + * * Returns: %TRUE if selection is non-empty **/ gboolean @@ -4721,7 +4881,7 @@ gtk_label_get_selection_bounds (GtkLabel *self, int start_index, end_index; int start_offset, end_offset; int len; - + start_index = MIN (self->select_info->selection_anchor, self->select_info->selection_end); end_index = MAX (self->select_info->selection_anchor, @@ -4734,7 +4894,7 @@ gtk_label_get_selection_bounds (GtkLabel *self, if (start_index > len) start_index = len; - + start_offset = g_utf8_strlen (self->text, start_index); end_offset = g_utf8_strlen (self->text, end_index); @@ -4744,7 +4904,7 @@ gtk_label_get_selection_bounds (GtkLabel *self, start_offset = end_offset; end_offset = tmp; } - + if (start) *start = start_offset; @@ -4759,7 +4919,7 @@ gtk_label_get_selection_bounds (GtkLabel *self, /** * gtk_label_get_layout: * @self: a #GtkLabel - * + * * Gets the #PangoLayout used to display the label. * The layout is useful to e.g. convert text positions to * pixel positions, in combination with gtk_label_get_layout_offsets(). @@ -4989,11 +5149,10 @@ get_better_cursor (GtkLabel *self, static int gtk_label_move_logically (GtkLabel *self, - int start, - int count) + int start, + int count) { - int offset = g_utf8_pointer_to_offset (self->text, - self->text + start); + int offset = g_utf8_pointer_to_offset (self->text, self->text + start); if (self->text) { @@ -5008,21 +5167,21 @@ gtk_label_move_logically (GtkLabel *self, log_attrs = pango_layout_get_log_attrs_readonly (self->layout, &n_attrs); while (count > 0 && offset < length) - { - do - offset++; - while (offset < length && !log_attrs[offset].is_cursor_position); - - count--; - } + { + do + offset++; + while (offset < length && !log_attrs[offset].is_cursor_position); + + count--; + } while (count < 0 && offset > 0) - { - do - offset--; - while (offset > 0 && !log_attrs[offset].is_cursor_position); - - count++; - } + { + do + offset--; + while (offset > 0 && !log_attrs[offset].is_cursor_position); + + count++; + } } return g_utf8_offset_to_pointer (self->text, offset) - self->text; @@ -5030,13 +5189,13 @@ gtk_label_move_logically (GtkLabel *self, static int gtk_label_move_visually (GtkLabel *self, - int start, - int count) + int start, + int count) { int index; index = start; - + while (count != 0) { int new_index, new_trailing; @@ -5046,8 +5205,8 @@ gtk_label_move_visually (GtkLabel *self, gtk_label_ensure_layout (self); g_object_get (gtk_widget_get_settings (GTK_WIDGET (self)), - "gtk-split-cursor", &split_cursor, - NULL); + "gtk-split-cursor", &split_cursor, + NULL); if (split_cursor) strong = TRUE; @@ -5071,80 +5230,26 @@ gtk_label_move_visually (GtkLabel *self, } if (count > 0) - { - pango_layout_move_cursor_visually (self->layout, strong, index, 0, 1, &new_index, &new_trailing); - count--; - } + { + pango_layout_move_cursor_visually (self->layout, strong, index, 0, 1, &new_index, &new_trailing); + count--; + } else - { - pango_layout_move_cursor_visually (self->layout, strong, index, 0, -1, &new_index, &new_trailing); - count++; - } + { + pango_layout_move_cursor_visually (self->layout, strong, index, 0, -1, &new_index, &new_trailing); + count++; + } if (new_index < 0 || new_index == G_MAXINT) - break; - - index = new_index; - - while (new_trailing--) - index = g_utf8_next_char (self->text + new_index) - self->text; - } - - return index; -} - -static int -gtk_label_move_forward_word (GtkLabel *self, - int start) -{ - int new_pos = g_utf8_pointer_to_offset (self->text, - self->text + start); - int length; - - length = g_utf8_strlen (self->text, -1); - if (new_pos < length) - { - const PangoLogAttr *log_attrs; - int n_attrs; - - gtk_label_ensure_layout (self); - - log_attrs = pango_layout_get_log_attrs_readonly (self->layout, &n_attrs); - - /* Find the next word end */ - new_pos++; - while (new_pos < n_attrs && !log_attrs[new_pos].is_word_end) - new_pos++; - } - - return g_utf8_offset_to_pointer (self->text, new_pos) - self->text; -} - - -static int -gtk_label_move_backward_word (GtkLabel *self, - int start) -{ - int new_pos = g_utf8_pointer_to_offset (self->text, - self->text + start); - - if (new_pos > 0) - { - const PangoLogAttr *log_attrs; - int n_attrs; - - gtk_label_ensure_layout (self); - - log_attrs = pango_layout_get_log_attrs_readonly (self->layout, &n_attrs); + break; - new_pos -= 1; + index = new_index; - /* Find the previous word beginning */ - while (new_pos > 0 && !log_attrs[new_pos].is_word_start) - new_pos--; + while (new_trailing--) + index = g_utf8_next_char (self->text + new_index) - self->text; } - return g_utf8_offset_to_pointer (self->text, new_pos) - self->text; + return index; } static void @@ -5275,135 +5380,6 @@ gtk_label_move_cursor (GtkLabel *self, gtk_label_select_region_index (self, new_pos, new_pos); } -static void -gtk_label_copy_clipboard (GtkLabel *self) -{ - if (self->text && self->select_info) - { - int start, end; - int len; - GdkClipboard *clipboard; - - start = MIN (self->select_info->selection_anchor, - self->select_info->selection_end); - end = MAX (self->select_info->selection_anchor, - self->select_info->selection_end); - - len = strlen (self->text); - - if (end > len) - end = len; - - if (start > len) - start = len; - - clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self)); - - if (start != end) - { - char *str = g_strndup (self->text + start, end - start); - gdk_clipboard_set_text (clipboard, str); - g_free (str); - } - else - { - GtkLabelLink *link; - - link = gtk_label_get_focus_link (self, NULL); - if (link) - gdk_clipboard_set_text (clipboard, link->uri); - } - } -} - -static void -gtk_label_select_all (GtkLabel *self) -{ - gtk_label_select_region_index (self, 0, strlen (self->text)); -} - -static void -gtk_label_activate_link_open (GtkWidget *widget, - const char *name, - GVariant *parameter) -{ - GtkLabel *self = GTK_LABEL (widget); - GtkLabelLink *link = self->select_info->context_link; - - if (link) - emit_activate_link (self, link); -} - -static void -gtk_label_activate_link_copy (GtkWidget *widget, - const char *name, - GVariant *parameter) -{ - GtkLabel *self = GTK_LABEL (widget); - GtkLabelLink *link = self->select_info->context_link; - - if (link) - { - GdkClipboard *clipboard; - - clipboard = gtk_widget_get_clipboard (widget); - gdk_clipboard_set_text (clipboard, link->uri); - } - else - g_print ("no link ?!\n"); -} - -static void -gtk_label_activate_clipboard_copy (GtkWidget *widget, - const char *name, - GVariant *parameter) -{ - g_signal_emit_by_name (widget, "copy-clipboard"); -} - -static void -gtk_label_activate_selection_select_all (GtkWidget *widget, - const char *name, - GVariant *parameter) -{ - gtk_label_select_all (GTK_LABEL (widget)); -} - -static void -gtk_label_nop (GtkWidget *widget, - const char *name, - GVariant *parameter) -{ -} - -static void -gtk_label_update_actions (GtkLabel *self) -{ - GtkWidget *widget = GTK_WIDGET (self); - gboolean has_selection; - GtkLabelLink *link; - - if (self->select_info) - { - has_selection = self->select_info->selection_anchor != self->select_info->selection_end; - link = self->select_info->active_link; - } - else - { - has_selection = FALSE; - link = gtk_label_get_focus_link (self, NULL); - } - - gtk_widget_action_set_enabled (widget, "clipboard.cut", FALSE); - gtk_widget_action_set_enabled (widget, "clipboard.copy", has_selection); - gtk_widget_action_set_enabled (widget, "clipboard.paste", FALSE); - gtk_widget_action_set_enabled (widget, "selection.select-all", - gtk_label_get_selectable (self)); - gtk_widget_action_set_enabled (widget, "selection.delete", FALSE); - gtk_widget_action_set_enabled (widget, "link.open", !has_selection && link); - gtk_widget_action_set_enabled (widget, "link.copy", !has_selection && link); -} - static GMenuModel * gtk_label_get_menu_model (GtkLabel *self) { @@ -5484,82 +5460,6 @@ gtk_label_do_popup (GtkLabel *self, gtk_popover_popup (GTK_POPOVER (self->popup_menu)); } -static void -gtk_label_popup_menu (GtkWidget *widget, - const char *action_name, - GVariant *parameters) -{ - GtkLabel *self = GTK_LABEL (widget); - - gtk_label_do_popup (self, -1, -1); -} - -static void -gtk_label_clear_links (GtkLabel *self) -{ - guint i; - - if (!self->select_info) - return; - - for (i = 0; i < self->select_info->n_links; i++) - link_free (&self->select_info->links[i]); - g_free (self->select_info->links); - self->select_info->links = NULL; - self->select_info->n_links = 0; - self->select_info->active_link = NULL; - gtk_widget_remove_css_class (GTK_WIDGET (self), "link"); -} - -static gboolean -gtk_label_activate_link (GtkLabel *self, - const char *uri) -{ - GtkWidget *widget = GTK_WIDGET (self); - GtkWidget *toplevel = GTK_WIDGET (gtk_widget_get_root (widget)); - - if (!GTK_IS_WINDOW (toplevel)) - return FALSE; - - gtk_show_uri (GTK_WINDOW (toplevel), uri, GDK_CURRENT_TIME); - - return TRUE; -} - -static void -emit_activate_link (GtkLabel *self, - GtkLabelLink *link) -{ - gboolean handled; - - g_signal_emit (self, signals[ACTIVATE_LINK], 0, link->uri, &handled); - - /* signal handler might have invalidated the layout */ - if (!self->layout) - return; - - if (handled && !link->visited && - self->select_info && self->select_info->links) - { - link->visited = TRUE; - update_link_state (self); - } -} - -static void -gtk_label_activate_current_link (GtkLabel *self) -{ - GtkLabelLink *link; - GtkWidget *widget = GTK_WIDGET (self); - - link = gtk_label_get_focus_link (self, NULL); - - if (link) - emit_activate_link (self, link); - else - gtk_widget_activate_default (widget); -} - /** * gtk_label_get_current_uri: * @self: a #GtkLabel @@ -5596,52 +5496,6 @@ gtk_label_get_current_uri (GtkLabel *self) return NULL; } -static gboolean -gtk_label_query_tooltip (GtkWidget *widget, - int x, - int y, - gboolean keyboard_tip, - GtkTooltip *tooltip) -{ - GtkLabel *self = GTK_LABEL (widget); - GtkLabelSelectionInfo *info = self->select_info; - int index = -1; - - if (info && info->links) - { - if (keyboard_tip) - { - if (info->selection_anchor == info->selection_end) - index = info->selection_anchor; - } - else - { - if (!get_layout_index (self, x, y, &index)) - index = -1; - } - - if (index != -1) - { - const int link_index = _gtk_label_get_link_at (self, index); - - if (link_index != -1) - { - const GtkLabelLink *link = &info->links[link_index]; - - if (link->title) - { - gtk_tooltip_set_markup (tooltip, link->title); - } - } - } - } - - return GTK_WIDGET_CLASS (gtk_label_parent_class)->query_tooltip (widget, - x, y, - keyboard_tip, - tooltip); -} - int _gtk_label_get_cursor_position (GtkLabel *self) { @@ -5862,7 +5716,7 @@ gtk_label_set_yalign (GtkLabel *self, { g_return_if_fail (GTK_IS_LABEL (self)); - yalign = CLAMP (yalign, 0.0, 1.0); + yalign = CLAMP (yalign, 0.0, 1.0); if (self->yalign == yalign) return; diff --git a/gtk/gtklabelprivate.h b/gtk/gtklabelprivate.h index 3533949e86..68ecc1a1e3 100644 --- a/gtk/gtklabelprivate.h +++ b/gtk/gtklabelprivate.h @@ -32,7 +32,7 @@ int _gtk_label_get_selection_bound (GtkLabel *label); int _gtk_label_get_n_links (GtkLabel *label); int _gtk_label_get_link_at (GtkLabel *label, int pos); -void _gtk_label_activate_link (GtkLabel *label, +void _gtk_label_activate_link (GtkLabel *label, int idx); const char *_gtk_label_get_link_uri (GtkLabel *label, int idx); @@ -44,7 +44,7 @@ gboolean _gtk_label_get_link_visited (GtkLabel *label, int idx); gboolean _gtk_label_get_link_focused (GtkLabel *label, int idx); - + G_END_DECLS #endif /* __GTK_LABEL_PRIVATE_H__ */ -- 2.30.2